rnnovaui 0.4.0 → 0.5.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rnnovaui",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "",
5
5
  "main": "src/index.js",
6
6
  "react-native": "src/index.js",
@@ -0,0 +1,683 @@
1
+ import React, { forwardRef, memo, useCallback, useMemo, useState } from "react";
2
+
3
+ import {
4
+ ActivityIndicator,
5
+ Pressable,
6
+ StyleSheet,
7
+ Text,
8
+ TextInput,
9
+ View,
10
+ } from "react-native";
11
+
12
+ import { useUITheme } from "../../theme";
13
+
14
+ const INPUT_VARIANTS = {
15
+ default: "default",
16
+ filled: "filled",
17
+ outlined: "outlined",
18
+ underline: "underline",
19
+ };
20
+
21
+ const INPUT_SIZES = {
22
+ sm: "sm",
23
+ md: "md",
24
+ lg: "lg",
25
+ };
26
+
27
+ const INPUT_STATES = {
28
+ default: "default",
29
+ error: "error",
30
+ success: "success",
31
+ disabled: "disabled",
32
+ };
33
+
34
+ const FALLBACK_COLORS = {
35
+ background: "#FFFFFF",
36
+ input: "#F7F7F7",
37
+ inputFocused: "#FFFFFF",
38
+ inputDisabled: "#EEEEEE",
39
+
40
+ text: "#111111",
41
+ textSecondary: "#525252",
42
+ textMuted: "#737373",
43
+ textDisabled: "#A3A3A3",
44
+
45
+ border: "#E5E5E5",
46
+ borderStrong: "#D4D4D4",
47
+
48
+ primary: "#FF5A1F",
49
+ danger: "#DC2626",
50
+ success: "#16A34A",
51
+
52
+ white: "#FFFFFF",
53
+ };
54
+
55
+ const UIInputComponent = forwardRef(function UIInput(
56
+ {
57
+ value,
58
+ defaultValue,
59
+
60
+ onChangeText,
61
+ onFocus,
62
+ onBlur,
63
+
64
+ placeholder,
65
+
66
+ placeholderTextColor,
67
+
68
+ label,
69
+ helperText,
70
+ errorText,
71
+ successText,
72
+
73
+ variant = "outlined",
74
+ size = "md",
75
+
76
+ floatingLabel = false,
77
+
78
+ disabled = false,
79
+ loading = false,
80
+
81
+ error = false,
82
+ success = false,
83
+
84
+ leftIcon = null,
85
+ rightIcon = null,
86
+
87
+ secureTextEntry = false,
88
+
89
+ showPasswordToggle = false,
90
+
91
+ labelStyle,
92
+ floatingLabelStyle,
93
+
94
+ helperStyle,
95
+ errorStyle,
96
+ successStyle,
97
+
98
+ inputStyle,
99
+ containerStyle,
100
+
101
+ focusedContainerStyle,
102
+ errorContainerStyle,
103
+ successContainerStyle,
104
+
105
+ activeBorderColor,
106
+
107
+ borderRadius,
108
+
109
+ fullWidth = true,
110
+
111
+ style,
112
+
113
+ testID,
114
+
115
+ ...props
116
+ },
117
+ ref,
118
+ ) {
119
+ const { theme } = useUITheme();
120
+
121
+ const colors = theme?.colors || {};
122
+
123
+ const [focused, setFocused] = useState(false);
124
+
125
+ const [passwordVisible, setPasswordVisible] = useState(false);
126
+
127
+ const isDisabled = disabled || loading;
128
+
129
+ const hasError = Boolean(error || errorText);
130
+
131
+ const hasSuccess = Boolean(success && !hasError);
132
+
133
+ const hasValue =
134
+ value !== undefined && value !== null && String(value).length > 0;
135
+
136
+ const shouldFloatLabel = floatingLabel && Boolean(focused || hasValue);
137
+
138
+ const safeVariant = INPUT_VARIANTS[variant]
139
+ ? variant
140
+ : INPUT_VARIANTS.outlined;
141
+
142
+ const safeSize = INPUT_SIZES[size] ? size : INPUT_SIZES.md;
143
+
144
+ const dimensions = useMemo(
145
+ () => getDimensions(safeSize, theme),
146
+ [safeSize, theme],
147
+ );
148
+
149
+ const resolvedColors = useMemo(
150
+ () =>
151
+ getInputColors(
152
+ colors,
153
+ safeVariant,
154
+ focused,
155
+ hasError,
156
+ hasSuccess,
157
+ isDisabled,
158
+ activeBorderColor,
159
+ ),
160
+ [
161
+ colors,
162
+ safeVariant,
163
+ focused,
164
+ hasError,
165
+ hasSuccess,
166
+ isDisabled,
167
+ activeBorderColor,
168
+ ],
169
+ );
170
+
171
+ const handleFocus = useCallback(
172
+ (event) => {
173
+ if (isDisabled) {
174
+ return;
175
+ }
176
+
177
+ setFocused(true);
178
+
179
+ onFocus?.(event);
180
+ },
181
+ [isDisabled, onFocus],
182
+ );
183
+
184
+ const handleBlur = useCallback(
185
+ (event) => {
186
+ setFocused(false);
187
+
188
+ onBlur?.(event);
189
+ },
190
+ [onBlur],
191
+ );
192
+
193
+ const togglePassword = useCallback(() => {
194
+ if (isDisabled) {
195
+ return;
196
+ }
197
+
198
+ setPasswordVisible((previous) => !previous);
199
+ }, [isDisabled]);
200
+
201
+ const resolvedSecure = secureTextEntry && !passwordVisible;
202
+
203
+ const resolvedPlaceholder =
204
+ placeholderTextColor || colors.textMuted || FALLBACK_COLORS.textMuted;
205
+
206
+ const resolvedLabel = label || placeholder;
207
+
208
+ const wrapperStyle = useMemo(
209
+ () => [
210
+ styles.wrapper,
211
+
212
+ {
213
+ width: fullWidth ? "100%" : undefined,
214
+ },
215
+
216
+ style,
217
+ ],
218
+ [fullWidth, style],
219
+ );
220
+
221
+ const containerStyles = useMemo(
222
+ () => [
223
+ styles.container,
224
+
225
+ {
226
+ minHeight: dimensions.height,
227
+
228
+ borderRadius: borderRadius ?? dimensions.borderRadius,
229
+
230
+ backgroundColor: resolvedColors.background,
231
+
232
+ borderColor: resolvedColors.border,
233
+
234
+ borderWidth: resolvedColors.borderWidth,
235
+
236
+ paddingHorizontal: dimensions.paddingHorizontal,
237
+ },
238
+
239
+ safeVariant === INPUT_VARIANTS.underline
240
+ ? styles.underlineContainer
241
+ : null,
242
+
243
+ focused ? focusedContainerStyle : null,
244
+
245
+ hasError ? errorContainerStyle : null,
246
+
247
+ hasSuccess ? successContainerStyle : null,
248
+
249
+ containerStyle,
250
+ ],
251
+ [
252
+ dimensions,
253
+ borderRadius,
254
+ resolvedColors,
255
+ safeVariant,
256
+ focused,
257
+ hasError,
258
+ hasSuccess,
259
+ focusedContainerStyle,
260
+ errorContainerStyle,
261
+ successContainerStyle,
262
+ containerStyle,
263
+ ],
264
+ );
265
+
266
+ const floatingLabelColor = hasError
267
+ ? colors.danger || FALLBACK_COLORS.danger
268
+ : hasSuccess
269
+ ? colors.success || FALLBACK_COLORS.success
270
+ : focused
271
+ ? activeBorderColor || colors.primary || FALLBACK_COLORS.primary
272
+ : colors.textSecondary || FALLBACK_COLORS.textSecondary;
273
+
274
+ return (
275
+ <View testID={testID} style={wrapperStyle}>
276
+ {!floatingLabel && label ? (
277
+ <Text
278
+ style={[
279
+ styles.label,
280
+
281
+ {
282
+ color: isDisabled
283
+ ? colors.textDisabled || FALLBACK_COLORS.textDisabled
284
+ : colors.text || FALLBACK_COLORS.text,
285
+ },
286
+
287
+ labelStyle,
288
+ ]}
289
+ >
290
+ {label}
291
+ </Text>
292
+ ) : null}
293
+
294
+ <View style={containerStyles}>
295
+ {floatingLabel &&
296
+ shouldFloatLabel &&
297
+ safeVariant === INPUT_VARIANTS.outlined &&
298
+ resolvedLabel ? (
299
+ <View
300
+ pointerEvents="none"
301
+ style={[
302
+ styles.floatingLabelBackground,
303
+
304
+ {
305
+ backgroundColor: resolvedColors.background,
306
+ },
307
+ ]}
308
+ >
309
+ <Text
310
+ numberOfLines={1}
311
+ style={[
312
+ styles.floatingLabel,
313
+
314
+ {
315
+ color: floatingLabelColor,
316
+ },
317
+
318
+ floatingLabelStyle,
319
+ ]}
320
+ >
321
+ {resolvedLabel}
322
+ </Text>
323
+ </View>
324
+ ) : null}
325
+
326
+ {leftIcon ? <View style={styles.leftIcon}>{leftIcon}</View> : null}
327
+
328
+ <TextInput
329
+ {...props}
330
+ ref={ref}
331
+ value={value}
332
+ defaultValue={defaultValue}
333
+ onChangeText={onChangeText}
334
+ onFocus={handleFocus}
335
+ onBlur={handleBlur}
336
+ placeholder={
337
+ floatingLabel && shouldFloatLabel ? undefined : placeholder
338
+ }
339
+ placeholderTextColor={resolvedPlaceholder}
340
+ secureTextEntry={resolvedSecure}
341
+ editable={!isDisabled}
342
+ selectionColor={colors.primary || FALLBACK_COLORS.primary}
343
+ style={[
344
+ styles.input,
345
+
346
+ {
347
+ color: isDisabled
348
+ ? colors.textDisabled || FALLBACK_COLORS.textDisabled
349
+ : colors.text || FALLBACK_COLORS.text,
350
+
351
+ fontSize: dimensions.fontSize,
352
+
353
+ lineHeight: dimensions.lineHeight,
354
+ },
355
+
356
+ floatingLabel && shouldFloatLabel ? styles.floatingInput : null,
357
+
358
+ inputStyle,
359
+ ]}
360
+ />
361
+
362
+ {loading ? (
363
+ <View style={styles.rightIcon}>
364
+ <ActivityIndicator
365
+ size="small"
366
+ color={colors.primary || FALLBACK_COLORS.primary}
367
+ />
368
+ </View>
369
+ ) : secureTextEntry && showPasswordToggle ? (
370
+ <Pressable
371
+ disabled={isDisabled}
372
+ onPress={togglePassword}
373
+ hitSlop={8}
374
+ style={styles.rightIcon}
375
+ >
376
+ {rightIcon}
377
+ </Pressable>
378
+ ) : rightIcon ? (
379
+ <View style={styles.rightIcon}>{rightIcon}</View>
380
+ ) : null}
381
+ </View>
382
+
383
+ {hasError ? (
384
+ <Text
385
+ style={[
386
+ styles.message,
387
+
388
+ {
389
+ color: colors.danger || FALLBACK_COLORS.danger,
390
+ },
391
+
392
+ errorStyle,
393
+ ]}
394
+ >
395
+ {errorText}
396
+ </Text>
397
+ ) : hasSuccess && successText ? (
398
+ <Text
399
+ style={[
400
+ styles.message,
401
+
402
+ {
403
+ color: colors.success || FALLBACK_COLORS.success,
404
+ },
405
+
406
+ successStyle,
407
+ ]}
408
+ >
409
+ {successText}
410
+ </Text>
411
+ ) : helperText ? (
412
+ <Text
413
+ style={[
414
+ styles.message,
415
+
416
+ {
417
+ color: colors.textSecondary || FALLBACK_COLORS.textSecondary,
418
+ },
419
+
420
+ helperStyle,
421
+ ]}
422
+ >
423
+ {helperText}
424
+ </Text>
425
+ ) : null}
426
+ </View>
427
+ );
428
+ });
429
+
430
+ function getInputColors(
431
+ colors,
432
+ variant,
433
+ focused,
434
+ hasError,
435
+ hasSuccess,
436
+ disabled,
437
+ activeBorderColor,
438
+ ) {
439
+ const primary = colors.primary || FALLBACK_COLORS.primary;
440
+
441
+ const border = colors.border || FALLBACK_COLORS.border;
442
+
443
+ if (disabled) {
444
+ return {
445
+ background: colors.inputDisabled || FALLBACK_COLORS.inputDisabled,
446
+
447
+ border: border,
448
+
449
+ borderWidth: variant === INPUT_VARIANTS.underline ? 0 : 1,
450
+ };
451
+ }
452
+
453
+ if (hasError) {
454
+ return {
455
+ background:
456
+ variant === INPUT_VARIANTS.filled
457
+ ? colors.input || FALLBACK_COLORS.input
458
+ : colors.background || FALLBACK_COLORS.background,
459
+
460
+ border: colors.danger || FALLBACK_COLORS.danger,
461
+
462
+ borderWidth: variant === INPUT_VARIANTS.underline ? 0 : 1,
463
+ };
464
+ }
465
+
466
+ if (hasSuccess) {
467
+ return {
468
+ background:
469
+ variant === INPUT_VARIANTS.filled
470
+ ? colors.input || FALLBACK_COLORS.input
471
+ : colors.background || FALLBACK_COLORS.background,
472
+
473
+ border: colors.success || FALLBACK_COLORS.success,
474
+
475
+ borderWidth: variant === INPUT_VARIANTS.underline ? 0 : 1,
476
+ };
477
+ }
478
+
479
+ if (focused) {
480
+ return {
481
+ background:
482
+ variant === INPUT_VARIANTS.filled
483
+ ? colors.inputFocused || FALLBACK_COLORS.inputFocused
484
+ : colors.background || FALLBACK_COLORS.background,
485
+
486
+ border: activeBorderColor || primary,
487
+
488
+ borderWidth: variant === INPUT_VARIANTS.underline ? 2 : 1,
489
+ };
490
+ }
491
+
492
+ if (variant === INPUT_VARIANTS.filled) {
493
+ return {
494
+ background: colors.input || FALLBACK_COLORS.input,
495
+
496
+ border: "transparent",
497
+
498
+ borderWidth: 0,
499
+ };
500
+ }
501
+
502
+ if (variant === INPUT_VARIANTS.underline) {
503
+ return {
504
+ background: "transparent",
505
+
506
+ border: border,
507
+
508
+ borderWidth: 0,
509
+ };
510
+ }
511
+
512
+ return {
513
+ background: colors.background || FALLBACK_COLORS.background,
514
+
515
+ border,
516
+
517
+ borderWidth: 1,
518
+ };
519
+ }
520
+
521
+ function getDimensions(size, theme) {
522
+ const inputSizes = theme?.sizes?.input || {};
523
+
524
+ const radius = theme?.radius || {};
525
+
526
+ switch (size) {
527
+ case INPUT_SIZES.sm:
528
+ return {
529
+ height: inputSizes.sm || 40,
530
+
531
+ paddingHorizontal: 12,
532
+
533
+ borderRadius: radius.sm ?? 8,
534
+
535
+ fontSize: 13,
536
+
537
+ lineHeight: 18,
538
+ };
539
+
540
+ case INPUT_SIZES.lg:
541
+ return {
542
+ height: inputSizes.lg || 56,
543
+
544
+ paddingHorizontal: 16,
545
+
546
+ borderRadius: radius.md ?? 12,
547
+
548
+ fontSize: 16,
549
+
550
+ lineHeight: 22,
551
+ };
552
+
553
+ case INPUT_SIZES.md:
554
+ default:
555
+ return {
556
+ height: inputSizes.md || 48,
557
+
558
+ paddingHorizontal: 14,
559
+
560
+ borderRadius: radius.sm ?? 10,
561
+
562
+ fontSize: 14,
563
+
564
+ lineHeight: 20,
565
+ };
566
+ }
567
+ }
568
+
569
+ const styles = StyleSheet.create({
570
+ wrapper: {
571
+ alignSelf: "stretch",
572
+ },
573
+
574
+ label: {
575
+ marginBottom: 7,
576
+
577
+ fontSize: 14,
578
+
579
+ lineHeight: 19,
580
+
581
+ fontWeight: "600",
582
+
583
+ includeFontPadding: false,
584
+ },
585
+
586
+ container: {
587
+ flexDirection: "row",
588
+
589
+ alignItems: "center",
590
+
591
+ position: "relative",
592
+
593
+ overflow: "visible",
594
+ },
595
+
596
+ underlineContainer: {
597
+ borderLeftWidth: 0,
598
+
599
+ borderRightWidth: 0,
600
+
601
+ borderTopWidth: 0,
602
+
603
+ borderRadius: 0,
604
+ },
605
+
606
+ input: {
607
+ flex: 1,
608
+
609
+ minWidth: 0,
610
+
611
+ paddingVertical: 0,
612
+
613
+ includeFontPadding: false,
614
+ },
615
+
616
+ floatingInput: {
617
+ paddingTop: 4,
618
+ },
619
+
620
+ floatingLabelBackground: {
621
+ position: "absolute",
622
+
623
+ left: 10,
624
+
625
+ top: -9,
626
+
627
+ zIndex: 10,
628
+
629
+ paddingHorizontal: 5,
630
+
631
+ alignSelf: "flex-start",
632
+
633
+ maxWidth: "80%",
634
+ },
635
+
636
+ floatingLabel: {
637
+ fontSize: 12,
638
+
639
+ lineHeight: 16,
640
+
641
+ fontWeight: "500",
642
+
643
+ includeFontPadding: false,
644
+ },
645
+
646
+ leftIcon: {
647
+ marginRight: 10,
648
+
649
+ alignItems: "center",
650
+
651
+ justifyContent: "center",
652
+ },
653
+
654
+ rightIcon: {
655
+ marginLeft: 10,
656
+
657
+ alignItems: "center",
658
+
659
+ justifyContent: "center",
660
+ },
661
+
662
+ message: {
663
+ marginTop: 6,
664
+
665
+ fontSize: 12,
666
+
667
+ lineHeight: 16,
668
+
669
+ includeFontPadding: false,
670
+ },
671
+ });
672
+
673
+ export const UIInput = memo(UIInputComponent);
674
+
675
+ UIInput.displayName = "UIInput";
676
+
677
+ export {
678
+ INPUT_VARIANTS as UIInputVariants,
679
+ INPUT_SIZES as UIInputSizes,
680
+ INPUT_STATES as UIInputStates,
681
+ };
682
+
683
+ export default UIInput;
@@ -0,0 +1,6 @@
1
+ export {
2
+ UIInput,
3
+ UIInputVariants,
4
+ UIInputSizes,
5
+ UIInputStates,
6
+ } from "./UIInput";
@@ -19,3 +19,4 @@ export {
19
19
  UIDividerTextPositions,
20
20
  UIDividerEnds,
21
21
  } from "./Divider";
22
+ export { UIInput, UIInputVariants, UIInputSizes, UIInputStates } from "./Input";
package/src/index.js CHANGED
@@ -33,4 +33,8 @@ export {
33
33
  UIDividerVariants,
34
34
  UIDividerTextPositions,
35
35
  UIDividerEnds,
36
+ UIInput,
37
+ UIInputVariants,
38
+ UIInputSizes,
39
+ UIInputStates,
36
40
  } from "./components";