nx-react-native-cli 2.7.0 → 3.0.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/lib/index.cjs +43 -43
- package/package.json +1 -1
- package/templates/19.7.0/apps/mobile/android/app/src/main/java/com/appsmobile/MainActivity.kt +5 -1
- package/templates/19.7.0/apps/mobile/ios/AppsMobile/AppDelegate.mm +6 -0
- package/templates/19.7.0/apps/mobile/ios/AppsMobile/Images.xcassets/AppIcon.appiconset/AppIcon-20@2x.png +0 -0
- package/templates/19.7.0/apps/mobile/ios/AppsMobile/Images.xcassets/AppIcon.appiconset/AppIcon-20@2x~ipad.png +0 -0
- package/templates/19.7.0/apps/mobile/ios/AppsMobile/Images.xcassets/AppIcon.appiconset/AppIcon-20@3x.png +0 -0
- package/templates/19.7.0/apps/mobile/ios/AppsMobile/Images.xcassets/AppIcon.appiconset/AppIcon-20~ipad.png +0 -0
- package/templates/19.7.0/apps/mobile/ios/AppsMobile/Images.xcassets/AppIcon.appiconset/AppIcon-29.png +0 -0
- package/templates/19.7.0/apps/mobile/ios/AppsMobile/Images.xcassets/AppIcon.appiconset/AppIcon-29@2x.png +0 -0
- package/templates/19.7.0/apps/mobile/ios/AppsMobile/Images.xcassets/AppIcon.appiconset/AppIcon-29@2x~ipad.png +0 -0
- package/templates/19.7.0/apps/mobile/ios/AppsMobile/Images.xcassets/AppIcon.appiconset/AppIcon-29@3x.png +0 -0
- package/templates/19.7.0/apps/mobile/ios/AppsMobile/Images.xcassets/AppIcon.appiconset/AppIcon-29~ipad.png +0 -0
- package/templates/19.7.0/apps/mobile/ios/AppsMobile/Images.xcassets/AppIcon.appiconset/AppIcon-40@2x.png +0 -0
- package/templates/19.7.0/apps/mobile/ios/AppsMobile/Images.xcassets/AppIcon.appiconset/AppIcon-40@2x~ipad.png +0 -0
- package/templates/19.7.0/apps/mobile/ios/AppsMobile/Images.xcassets/AppIcon.appiconset/AppIcon-40@3x.png +0 -0
- package/templates/19.7.0/apps/mobile/ios/AppsMobile/Images.xcassets/AppIcon.appiconset/AppIcon-40~ipad.png +0 -0
- package/templates/19.7.0/apps/mobile/ios/AppsMobile/Images.xcassets/AppIcon.appiconset/AppIcon-60@2x~car.png +0 -0
- package/templates/19.7.0/apps/mobile/ios/AppsMobile/Images.xcassets/AppIcon.appiconset/AppIcon-60@3x~car.png +0 -0
- package/templates/19.7.0/apps/mobile/ios/AppsMobile/Images.xcassets/AppIcon.appiconset/AppIcon-83.5@2x~ipad.png +0 -0
- package/templates/19.7.0/apps/mobile/ios/AppsMobile/Images.xcassets/AppIcon.appiconset/AppIcon@2x.png +0 -0
- package/templates/19.7.0/apps/mobile/ios/AppsMobile/Images.xcassets/AppIcon.appiconset/AppIcon@2x~ipad.png +0 -0
- package/templates/19.7.0/apps/mobile/ios/AppsMobile/Images.xcassets/AppIcon.appiconset/AppIcon@3x.png +0 -0
- package/templates/19.7.0/apps/mobile/ios/AppsMobile/Images.xcassets/AppIcon.appiconset/AppIcon~ios-marketing.png +0 -0
- package/templates/19.7.0/apps/mobile/ios/AppsMobile/Images.xcassets/AppIcon.appiconset/AppIcon~ipad.png +0 -0
- package/templates/19.7.0/apps/mobile/ios/AppsMobile/Images.xcassets/AppIcon.appiconset/Contents.json +134 -0
- package/templates/19.7.0/apps/mobile/ios/AppsMobile/Images.xcassets/Contents.json +6 -0
- package/templates/21.2.2/apps/mobile/android/app/src/main/java/com/mobile/MainActivity.kt +5 -1
- package/templates/21.2.2/apps/mobile/ios/Mobile/AppDelegate.mm +6 -0
- package/templates/21.2.2/apps/mobile/ios/Mobile/Images.xcassets/AppIcon.appiconset/AppIcon-20@2x.png +0 -0
- package/templates/21.2.2/apps/mobile/ios/Mobile/Images.xcassets/AppIcon.appiconset/AppIcon-20@2x~ipad.png +0 -0
- package/templates/21.2.2/apps/mobile/ios/Mobile/Images.xcassets/AppIcon.appiconset/AppIcon-20@3x.png +0 -0
- package/templates/21.2.2/apps/mobile/ios/Mobile/Images.xcassets/AppIcon.appiconset/AppIcon-20~ipad.png +0 -0
- package/templates/21.2.2/apps/mobile/ios/Mobile/Images.xcassets/AppIcon.appiconset/AppIcon-29.png +0 -0
- package/templates/21.2.2/apps/mobile/ios/Mobile/Images.xcassets/AppIcon.appiconset/AppIcon-29@2x.png +0 -0
- package/templates/21.2.2/apps/mobile/ios/Mobile/Images.xcassets/AppIcon.appiconset/AppIcon-29@2x~ipad.png +0 -0
- package/templates/21.2.2/apps/mobile/ios/Mobile/Images.xcassets/AppIcon.appiconset/AppIcon-29@3x.png +0 -0
- package/templates/21.2.2/apps/mobile/ios/Mobile/Images.xcassets/AppIcon.appiconset/AppIcon-29~ipad.png +0 -0
- package/templates/21.2.2/apps/mobile/ios/Mobile/Images.xcassets/AppIcon.appiconset/AppIcon-40@2x.png +0 -0
- package/templates/21.2.2/apps/mobile/ios/Mobile/Images.xcassets/AppIcon.appiconset/AppIcon-40@2x~ipad.png +0 -0
- package/templates/21.2.2/apps/mobile/ios/Mobile/Images.xcassets/AppIcon.appiconset/AppIcon-40@3x.png +0 -0
- package/templates/21.2.2/apps/mobile/ios/Mobile/Images.xcassets/AppIcon.appiconset/AppIcon-40~ipad.png +0 -0
- package/templates/21.2.2/apps/mobile/ios/Mobile/Images.xcassets/AppIcon.appiconset/AppIcon-60@2x~car.png +0 -0
- package/templates/21.2.2/apps/mobile/ios/Mobile/Images.xcassets/AppIcon.appiconset/AppIcon-60@3x~car.png +0 -0
- package/templates/21.2.2/apps/mobile/ios/Mobile/Images.xcassets/AppIcon.appiconset/AppIcon-83.5@2x~ipad.png +0 -0
- package/templates/21.2.2/apps/mobile/ios/Mobile/Images.xcassets/AppIcon.appiconset/AppIcon@2x.png +0 -0
- package/templates/21.2.2/apps/mobile/ios/Mobile/Images.xcassets/AppIcon.appiconset/AppIcon@2x~ipad.png +0 -0
- package/templates/21.2.2/apps/mobile/ios/Mobile/Images.xcassets/AppIcon.appiconset/AppIcon@3x.png +0 -0
- package/templates/21.2.2/apps/mobile/ios/Mobile/Images.xcassets/AppIcon.appiconset/AppIcon~ios-marketing.png +0 -0
- package/templates/21.2.2/apps/mobile/ios/Mobile/Images.xcassets/AppIcon.appiconset/AppIcon~ipad.png +0 -0
- package/templates/21.2.2/apps/mobile/ios/Mobile/Images.xcassets/AppIcon.appiconset/Contents.json +134 -0
- package/templates/21.2.2/apps/mobile/ios/Mobile/Images.xcassets/Contents.json +6 -0
- package/templates/shared/apps/mobile/android/app/src/main/AndroidManifest.xml +28 -0
- package/templates/shared/apps/mobile/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +6 -0
- package/templates/shared/apps/mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
- package/templates/shared/apps/mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher_background.png +0 -0
- package/templates/shared/apps/mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png +0 -0
- package/templates/shared/apps/mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher_monochrome.png +0 -0
- package/templates/shared/apps/mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
- package/templates/shared/apps/mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher_background.png +0 -0
- package/templates/shared/apps/mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png +0 -0
- package/templates/shared/apps/mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher_monochrome.png +0 -0
- package/templates/shared/apps/mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
- package/templates/shared/apps/mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png +0 -0
- package/templates/shared/apps/mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png +0 -0
- package/templates/shared/apps/mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher_monochrome.png +0 -0
- package/templates/shared/apps/mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
- package/templates/shared/apps/mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png +0 -0
- package/templates/shared/apps/mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png +0 -0
- package/templates/shared/apps/mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_monochrome.png +0 -0
- package/templates/shared/apps/mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
- package/templates/shared/apps/mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png +0 -0
- package/templates/shared/apps/mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png +0 -0
- package/templates/shared/apps/mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_monochrome.png +0 -0
- package/templates/shared/apps/mobile/android/app/src/main/res/styles.xml +14 -0
- package/templates/shared/apps/mobile/run-android.sh +2 -1
- package/templates/shared/apps/mobile/scripts/setup-ios-dev-scheme.rb +101 -1
- package/templates/shared/apps/mobile/src/app/index.tsx +16 -47
- package/templates/shared/apps/mobile/src/app/query-client.ts +40 -0
- package/templates/shared/apps/mobile/src/assets/images/logo.png +0 -0
- package/templates/shared/apps/mobile/src/components/atoms/AlertManager/alert-manager.component.tsx +134 -0
- package/templates/shared/apps/mobile/src/components/atoms/AlertManager/alert-manager.types.ts +18 -0
- package/templates/shared/apps/mobile/src/components/atoms/AlertManager/alert.service.ts +27 -0
- package/templates/shared/apps/mobile/src/components/atoms/AlertManager/index.ts +3 -0
- package/templates/shared/apps/mobile/src/components/atoms/BottomSheet/bottom-sheet.component.tsx +14 -8
- package/templates/shared/apps/mobile/src/components/atoms/Button/button.component.tsx +1 -1
- package/templates/shared/apps/mobile/src/components/atoms/DateModalInput/date-modal-input.component.tsx +69 -0
- package/templates/shared/apps/mobile/src/components/atoms/DateModalInput/index.ts +1 -0
- package/templates/shared/apps/mobile/src/components/atoms/DatePicker/date-picker.component.tsx +44 -0
- package/templates/shared/apps/mobile/src/components/atoms/DatePicker/index.ts +1 -0
- package/templates/shared/apps/mobile/src/components/atoms/DateTextInput/date-text-input.component.tsx +218 -0
- package/templates/shared/apps/mobile/src/components/atoms/DateTextInput/index.ts +1 -0
- package/templates/shared/apps/mobile/src/components/atoms/Divider/divider-component.tsx +1 -1
- package/templates/shared/apps/mobile/src/components/atoms/GradientBackground/gradient-background.component.tsx +45 -0
- package/templates/shared/apps/mobile/src/components/atoms/GradientBackground/index.ts +1 -0
- package/templates/shared/apps/mobile/src/components/atoms/InputLayout/input-layout.component.tsx +12 -4
- package/templates/shared/apps/mobile/src/components/atoms/KeyboardAccessory/keyboard-accessory.component.tsx +6 -3
- package/templates/shared/apps/mobile/src/components/atoms/KeyboardAwareScrollView/keyboard-aware-scroll-view.component.tsx +1 -0
- package/templates/shared/apps/mobile/src/components/atoms/Modal/modal.component.tsx +2 -0
- package/templates/shared/apps/mobile/src/components/atoms/ScreenLoader/screen-loader.component.tsx +6 -1
- package/templates/shared/apps/mobile/src/components/atoms/SelectDropdown/index.ts +1 -0
- package/templates/shared/apps/mobile/src/components/atoms/SelectDropdown/select-dropdown.component.tsx +223 -0
- package/templates/shared/apps/mobile/src/components/atoms/Skeleton/skeleton.component.tsx +1 -1
- package/templates/shared/apps/mobile/src/components/atoms/TextInput/bottom-sheet-text-input.component.tsx +4 -3
- package/templates/shared/apps/mobile/src/components/atoms/TextInput/text-input.component.tsx +8 -4
- package/templates/shared/apps/mobile/src/components/atoms/ThemeManager/index.ts +1 -0
- package/templates/shared/apps/mobile/src/components/atoms/ThemeManager/theme-manager.component.tsx +27 -0
- package/templates/shared/apps/mobile/src/components/atoms/ToastManager/index.ts +3 -0
- package/templates/shared/apps/mobile/src/components/atoms/ToastManager/toast-manager.component.tsx +109 -0
- package/templates/shared/apps/mobile/src/components/atoms/ToastManager/toast-manager.types.ts +10 -0
- package/templates/shared/apps/mobile/src/components/atoms/ToastManager/toast.service.ts +27 -0
- package/templates/shared/apps/mobile/src/components/atoms/Typography/typography.component.tsx +1 -1
- package/templates/shared/apps/mobile/src/components/atoms/index.ts +8 -0
- package/templates/shared/apps/mobile/src/components/molecules/BackButton/back-button.component.tsx +1 -1
- package/templates/shared/apps/mobile/src/components/molecules/ScreenContainer/screen-container.component.tsx +4 -24
- package/templates/shared/apps/mobile/src/components/molecules/ScreenHeader/screen-header.component.tsx +2 -2
- package/templates/shared/apps/mobile/src/hooks/index.ts +1 -0
- package/templates/shared/apps/mobile/src/hooks/usePushNotifications.hook.ts +104 -0
- package/templates/shared/apps/mobile/src/hooks/useToggleDarkMode.hook.tsx +24 -2
- package/templates/shared/apps/mobile/src/icons/alert-triangle.svg +5 -0
- package/templates/shared/apps/mobile/src/icons/check-circle.svg +4 -0
- package/templates/shared/apps/mobile/src/icons/chevron-down.svg +1 -0
- package/templates/shared/apps/mobile/src/icons/chevron-right.svg +1 -0
- package/templates/shared/apps/mobile/src/icons/index.ts +18 -1
- package/templates/shared/apps/mobile/src/icons/info.svg +5 -0
- package/templates/shared/apps/mobile/src/icons/x-circle.svg +5 -0
- package/templates/shared/apps/mobile/src/routes/index.tsx +26 -15
- package/templates/shared/apps/mobile/src/screens/LandingScreen/landing.screen.tsx +232 -8
- package/templates/shared/apps/mobile/src/stores/local-storage.store.ts +9 -5
- package/templates/shared/apps/mobile/src/stores/theme.slice.ts +15 -0
- package/templates/shared/apps/mobile/src/stores/user.slice.ts +5 -1
- package/templates/shared/apps/mobile/src/tailwind/index.ts +3 -3
- package/templates/shared/apps/mobile/tailwind.config.js +14 -0
- package/templates/shared/patches/react-native-animatable+1.4.0.patch +71 -0
- package/templates/shared/apps/mobile/src/assets/images/.gitkeep +0 -0
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import dayjs from 'dayjs';
|
|
2
|
+
import customParseFormat from 'dayjs/plugin/customParseFormat';
|
|
3
|
+
import React, { ReactNode, Ref, useCallback, useEffect, useRef, useState } from 'react';
|
|
4
|
+
import { Pressable, StyleProp, TextInput, ViewStyle } from 'react-native';
|
|
5
|
+
|
|
6
|
+
import { Typography } from '@/components/atoms/Typography';
|
|
7
|
+
import CONFIG from '@/config';
|
|
8
|
+
import {
|
|
9
|
+
defaultInputContainerStyle,
|
|
10
|
+
defaultInputTextStyle,
|
|
11
|
+
focusedInputStyle,
|
|
12
|
+
tw,
|
|
13
|
+
} from '@/tailwind';
|
|
14
|
+
|
|
15
|
+
dayjs.extend(customParseFormat);
|
|
16
|
+
|
|
17
|
+
const DATE_FORMAT = 'MM/DD/YYYY';
|
|
18
|
+
const GUIDE_TEMPLATE = 'MM/DD/YYYY';
|
|
19
|
+
const MAX_DIGITS = 8;
|
|
20
|
+
const MONTH_SEPARATOR_INDEX = 2;
|
|
21
|
+
const DAY_SEPARATOR_INDEX = 4;
|
|
22
|
+
|
|
23
|
+
const fontFamily = CONFIG.IS_IOS ? { fontFamily: 'Menlo' } : { fontFamily: 'monospace' };
|
|
24
|
+
|
|
25
|
+
type Props = {
|
|
26
|
+
error?: string;
|
|
27
|
+
onChange: (date: Date | undefined) => void;
|
|
28
|
+
onValidationError?: (error: string | undefined) => void;
|
|
29
|
+
ref?: Ref<TextInput>;
|
|
30
|
+
renderRight?: (date: Date) => ReactNode;
|
|
31
|
+
value: Date | undefined;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const MONTH_FIRST_DIGIT_INDEX = 0;
|
|
35
|
+
const MONTH_SECOND_DIGIT_INDEX = 1;
|
|
36
|
+
const DAY_FIRST_DIGIT_INDEX = 2;
|
|
37
|
+
const DAY_SECOND_DIGIT_INDEX = 3;
|
|
38
|
+
const YEAR_FIRST_DIGIT_INDEX = 4;
|
|
39
|
+
const MAX_MONTH_FIRST_DIGIT = 1;
|
|
40
|
+
const MAX_DAY_FIRST_DIGIT = 3;
|
|
41
|
+
|
|
42
|
+
function isDigitValid(digit: string, index: number, previous: string): boolean {
|
|
43
|
+
const d = Number(digit);
|
|
44
|
+
|
|
45
|
+
if (index === MONTH_FIRST_DIGIT_INDEX) {
|
|
46
|
+
return d <= MAX_MONTH_FIRST_DIGIT;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (index === MONTH_SECOND_DIGIT_INDEX) {
|
|
50
|
+
return previous[0] === '0' ? d >= 1 : d <= MONTH_SEPARATOR_INDEX;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (index === DAY_FIRST_DIGIT_INDEX) {
|
|
54
|
+
return d <= MAX_DAY_FIRST_DIGIT;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (index === DAY_SECOND_DIGIT_INDEX) {
|
|
58
|
+
return previous[MONTH_SEPARATOR_INDEX] === '3' ? d <= 1 : d >= 0;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (index === YEAR_FIRST_DIGIT_INDEX) {
|
|
62
|
+
return d === 1 || d === MONTH_SEPARATOR_INDEX;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function filterValidDigits(raw: string): string {
|
|
69
|
+
let result = '';
|
|
70
|
+
|
|
71
|
+
for (let i = 0; i < raw.length && result.length < MAX_DIGITS; i += 1) {
|
|
72
|
+
if (isDigitValid(raw[i], result.length, result)) {
|
|
73
|
+
result += raw[i];
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return result;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function extractDigits(date: Date | undefined): string {
|
|
81
|
+
if (!date) {
|
|
82
|
+
return '';
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return dayjs(date).format(DATE_FORMAT).replace(/\D/g, '');
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function buildFilledPortion(digits: string): string {
|
|
89
|
+
let result = '';
|
|
90
|
+
|
|
91
|
+
for (let i = 0; i < digits.length && i < MAX_DIGITS; i += 1) {
|
|
92
|
+
if (i === MONTH_SEPARATOR_INDEX || i === DAY_SEPARATOR_INDEX) {
|
|
93
|
+
result += '/';
|
|
94
|
+
}
|
|
95
|
+
result += digits[i];
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return result;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function buildGuideSuffix(digits: string): string {
|
|
102
|
+
const filled = buildFilledPortion(digits);
|
|
103
|
+
|
|
104
|
+
return GUIDE_TEMPLATE.slice(filled.length);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function parseDate(digits: string): Date | undefined {
|
|
108
|
+
if (digits.length !== MAX_DIGITS) {
|
|
109
|
+
return undefined;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const masked = buildFilledPortion(digits);
|
|
113
|
+
const parsed = dayjs(masked, DATE_FORMAT, true);
|
|
114
|
+
|
|
115
|
+
if (parsed.isValid()) {
|
|
116
|
+
return parsed.toDate();
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return undefined;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function DateTextInput(props: Props) {
|
|
123
|
+
const { error, onChange, onValidationError, ref, renderRight, value } = props;
|
|
124
|
+
const [digits, setDigits] = useState(() => extractDigits(value));
|
|
125
|
+
const [isFocused, setIsFocused] = useState(false);
|
|
126
|
+
const [invalidDateError, setInvalidDateError] = useState<string | undefined>();
|
|
127
|
+
const inputRef = useRef<TextInput>(null);
|
|
128
|
+
|
|
129
|
+
useEffect(() => {
|
|
130
|
+
if (!ref) {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (typeof ref === 'function') {
|
|
135
|
+
ref(inputRef.current);
|
|
136
|
+
} else {
|
|
137
|
+
(ref as React.MutableRefObject<TextInput | null>).current = inputRef.current;
|
|
138
|
+
}
|
|
139
|
+
}, [ref]);
|
|
140
|
+
|
|
141
|
+
const filledPortion = buildFilledPortion(digits);
|
|
142
|
+
const guideSuffix = buildGuideSuffix(digits);
|
|
143
|
+
const parsedDate = parseDate(digits);
|
|
144
|
+
const displayError = invalidDateError || error;
|
|
145
|
+
|
|
146
|
+
const containerStyle: StyleProp<ViewStyle> = [
|
|
147
|
+
defaultInputContainerStyle,
|
|
148
|
+
tw`dark:border-divider dark:bg-surface items-center`,
|
|
149
|
+
focusedInputStyle(isFocused),
|
|
150
|
+
displayError && tw`border-red-500`,
|
|
151
|
+
];
|
|
152
|
+
|
|
153
|
+
const handleChangeText = useCallback(
|
|
154
|
+
(text: string) => {
|
|
155
|
+
const rawDigits = text.replace(/\D/g, '');
|
|
156
|
+
const newDigits = filterValidDigits(rawDigits);
|
|
157
|
+
setDigits(newDigits);
|
|
158
|
+
|
|
159
|
+
if (newDigits.length === MAX_DIGITS) {
|
|
160
|
+
const masked = buildFilledPortion(newDigits);
|
|
161
|
+
const parsed = dayjs(masked, DATE_FORMAT, true);
|
|
162
|
+
|
|
163
|
+
if (parsed.isValid()) {
|
|
164
|
+
setInvalidDateError(undefined);
|
|
165
|
+
onValidationError?.(undefined);
|
|
166
|
+
onChange(parsed.toDate());
|
|
167
|
+
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const errorMsg = 'Please enter a valid date';
|
|
172
|
+
setInvalidDateError(errorMsg);
|
|
173
|
+
onValidationError?.(errorMsg);
|
|
174
|
+
} else {
|
|
175
|
+
setInvalidDateError(undefined);
|
|
176
|
+
onValidationError?.(undefined);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
onChange(undefined);
|
|
180
|
+
},
|
|
181
|
+
[onChange, onValidationError],
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
const handleBlur = useCallback(() => {
|
|
185
|
+
setIsFocused(false);
|
|
186
|
+
}, []);
|
|
187
|
+
|
|
188
|
+
const handleFocus = useCallback(() => {
|
|
189
|
+
setIsFocused(true);
|
|
190
|
+
}, []);
|
|
191
|
+
|
|
192
|
+
const handlePress = useCallback(() => {
|
|
193
|
+
inputRef.current?.focus();
|
|
194
|
+
}, []);
|
|
195
|
+
|
|
196
|
+
return (
|
|
197
|
+
<Pressable style={containerStyle} onPress={handlePress}>
|
|
198
|
+
<TextInput
|
|
199
|
+
ref={inputRef}
|
|
200
|
+
keyboardType="number-pad"
|
|
201
|
+
style={[
|
|
202
|
+
defaultInputTextStyle,
|
|
203
|
+
tw`dark:text-foreground`,
|
|
204
|
+
{ ...fontFamily, opacity: 0, position: 'absolute' },
|
|
205
|
+
]}
|
|
206
|
+
value={digits}
|
|
207
|
+
onBlur={handleBlur}
|
|
208
|
+
onChangeText={handleChangeText}
|
|
209
|
+
onFocus={handleFocus}
|
|
210
|
+
/>
|
|
211
|
+
<Typography style={[defaultInputTextStyle, tw`dark:text-foreground`, fontFamily]}>
|
|
212
|
+
{filledPortion}
|
|
213
|
+
<Typography style={[tw`text-placeholder`, { ...fontFamily }]}>{guideSuffix}</Typography>
|
|
214
|
+
</Typography>
|
|
215
|
+
{parsedDate && renderRight?.(parsedDate)}
|
|
216
|
+
</Pressable>
|
|
217
|
+
);
|
|
218
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './date-text-input.component';
|
|
@@ -9,5 +9,5 @@ type Props = DefaultComponentProps & {};
|
|
|
9
9
|
export function Divider(props: Props) {
|
|
10
10
|
const { style } = props;
|
|
11
11
|
|
|
12
|
-
return <View style={[tw`h-[1px] w-full bg-gray-200`, style]}></View>;
|
|
12
|
+
return <View style={[tw`dark:bg-divider h-[1px] w-full bg-gray-200`, style]}></View>;
|
|
13
13
|
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/* eslint-disable no-magic-numbers */
|
|
2
|
+
import { Canvas, Fill, LinearGradient, vec } from '@shopify/react-native-skia';
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import { StyleSheet, useWindowDimensions } from 'react-native';
|
|
5
|
+
|
|
6
|
+
import { useLocalStorageStore } from '@/stores';
|
|
7
|
+
import { colors } from '@/tailwind';
|
|
8
|
+
|
|
9
|
+
type Props = {
|
|
10
|
+
variant?: 'bottomSheet' | 'login' | 'welcome';
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const DARK_GRADIENTS: Record<string, string[]> = {
|
|
14
|
+
bottomSheet: [colors.sheet, colors.background],
|
|
15
|
+
login: [colors.secondary[950], colors.background, colors.secondary[950]],
|
|
16
|
+
welcome: [colors.background, colors.secondary[950], colors.background],
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const LIGHT_GRADIENTS: Record<string, string[]> = {
|
|
20
|
+
bottomSheet: [colors.gray[100], colors.white],
|
|
21
|
+
login: [colors.gray[50], colors.white, colors.gray[50]],
|
|
22
|
+
welcome: [colors.white, colors.gray[50], colors.white],
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export function GradientBackground(props: Props) {
|
|
26
|
+
const { variant = 'login' } = props;
|
|
27
|
+
const colorScheme = useLocalStorageStore((s) => s.colorScheme);
|
|
28
|
+
const isDark = colorScheme === 'dark';
|
|
29
|
+
const { height, width } = useWindowDimensions();
|
|
30
|
+
|
|
31
|
+
const gradientColors = isDark ? DARK_GRADIENTS[variant] : LIGHT_GRADIENTS[variant];
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<Canvas style={StyleSheet.absoluteFill}>
|
|
35
|
+
{/* Base linear gradient */}
|
|
36
|
+
<Fill>
|
|
37
|
+
<LinearGradient
|
|
38
|
+
colors={gradientColors}
|
|
39
|
+
end={vec(width / 2, height)}
|
|
40
|
+
start={vec(width / 2, 0)}
|
|
41
|
+
/>
|
|
42
|
+
</Fill>
|
|
43
|
+
</Canvas>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './gradient-background.component';
|
package/templates/shared/apps/mobile/src/components/atoms/InputLayout/input-layout.component.tsx
CHANGED
|
@@ -9,24 +9,32 @@ type Props = DefaultComponentProps & {
|
|
|
9
9
|
children?: React.ReactNode;
|
|
10
10
|
isRequired?: boolean;
|
|
11
11
|
label?: string;
|
|
12
|
+
subtitle?: string;
|
|
12
13
|
textStyle?: StyleProp<TextStyle>;
|
|
13
14
|
};
|
|
14
15
|
|
|
15
16
|
export function InputLayout(props: Props) {
|
|
16
|
-
const { children, error, isRequired, label, style, textStyle } = props;
|
|
17
|
+
const { children, error, isRequired, label, style, subtitle, textStyle } = props;
|
|
17
18
|
|
|
18
19
|
return (
|
|
19
20
|
<View style={[style]}>
|
|
20
21
|
{label && (
|
|
21
|
-
<Typography style={[tw`mb-2 text-gray-600`, textStyle]}>
|
|
22
|
+
<Typography style={[tw`mb-2 text-gray-600 dark:text-gray-300`, textStyle]}>
|
|
22
23
|
{label}
|
|
23
24
|
{isRequired && <Typography style={tw`text-red-600`}>{isRequired && '*'}</Typography>}
|
|
24
25
|
</Typography>
|
|
25
26
|
)}
|
|
26
27
|
{children}
|
|
27
28
|
{!!error && (
|
|
28
|
-
<View style={tw`items-
|
|
29
|
-
<Typography style={tw`text-right text-red-500`}>{error}</Typography>
|
|
29
|
+
<View style={tw`mt-1 items-start`}>
|
|
30
|
+
<Typography style={tw`text-right text-xs text-red-500`}>{error}</Typography>
|
|
31
|
+
</View>
|
|
32
|
+
)}
|
|
33
|
+
{!!subtitle && !error && (
|
|
34
|
+
<View style={tw`mt-1 items-start`}>
|
|
35
|
+
<Typography style={tw`dark:text-subtitle text-right text-xs text-gray-400`}>
|
|
36
|
+
{subtitle}
|
|
37
|
+
</Typography>
|
|
30
38
|
</View>
|
|
31
39
|
)}
|
|
32
40
|
</View>
|
|
@@ -15,9 +15,12 @@ export function KeyboardAccessory(props: Props) {
|
|
|
15
15
|
|
|
16
16
|
return (
|
|
17
17
|
<InputAccessoryView nativeID={nativeID}>
|
|
18
|
-
<View style={tw`flex-row items-center justify-end bg-[#313132]
|
|
19
|
-
<Button
|
|
20
|
-
|
|
18
|
+
<View style={tw`flex-row items-center justify-end bg-gray-100 px-2 dark:bg-[#313132]`}>
|
|
19
|
+
<Button
|
|
20
|
+
buttonStyle={tw`my-2 rounded-lg bg-gray-200 dark:bg-[#717172]`}
|
|
21
|
+
onPress={() => Keyboard.dismiss()}
|
|
22
|
+
>
|
|
23
|
+
<KeyboardHideIcon style={tw`text-gray-600 dark:text-white`} />
|
|
21
24
|
</Button>
|
|
22
25
|
</View>
|
|
23
26
|
</InputAccessoryView>
|
package/templates/shared/apps/mobile/src/components/atoms/ScreenLoader/screen-loader.component.tsx
CHANGED
|
@@ -10,7 +10,12 @@ export function ScreenLoader(props: Props) {
|
|
|
10
10
|
const { style } = props;
|
|
11
11
|
|
|
12
12
|
return (
|
|
13
|
-
<View
|
|
13
|
+
<View
|
|
14
|
+
style={[
|
|
15
|
+
tw`dark:bg-background h-full w-full items-center justify-center bg-gray-50 p-8`,
|
|
16
|
+
style,
|
|
17
|
+
]}
|
|
18
|
+
>
|
|
14
19
|
<ActivityIndicator color={colors.primary[400]} />
|
|
15
20
|
</View>
|
|
16
21
|
);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './select-dropdown.component';
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
/* eslint-disable max-params */
|
|
2
|
+
/* eslint-disable no-magic-numbers */
|
|
3
|
+
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
4
|
+
import { Dimensions, Pressable, ScrollView, View } from 'react-native';
|
|
5
|
+
|
|
6
|
+
import { Modal, Typography } from '@/components';
|
|
7
|
+
import { ChevronDownIcon, ChevronRightIcon } from '@/icons';
|
|
8
|
+
import { colors, defaultInputContainerStyle, defaultInputTextStyle, tw } from '@/tailwind';
|
|
9
|
+
import { DefaultComponentProps } from '@/types';
|
|
10
|
+
|
|
11
|
+
const DROPDOWN_OFFSET = 8;
|
|
12
|
+
const MAX_MENU_HEIGHT = 320;
|
|
13
|
+
const OPTION_ROW_HEIGHT = 48;
|
|
14
|
+
|
|
15
|
+
type DropdownOption = {
|
|
16
|
+
id: string;
|
|
17
|
+
label: string;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
type DropdownLayout = {
|
|
21
|
+
height: number;
|
|
22
|
+
width: number;
|
|
23
|
+
x: number;
|
|
24
|
+
y: number;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
type Props = DefaultComponentProps & {
|
|
28
|
+
label?: string;
|
|
29
|
+
options: DropdownOption[];
|
|
30
|
+
placeholder?: string;
|
|
31
|
+
selectedId?: string;
|
|
32
|
+
onSelect: (id: string) => void;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
type OptionItemProps = {
|
|
36
|
+
isLast: boolean;
|
|
37
|
+
isSelected: boolean;
|
|
38
|
+
label: string;
|
|
39
|
+
onPress: () => void;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
function OptionItem(props: OptionItemProps) {
|
|
43
|
+
const { isLast, isSelected, label, onPress } = props;
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<Pressable
|
|
47
|
+
style={[
|
|
48
|
+
tw`flex-row items-center justify-between px-4 py-3`,
|
|
49
|
+
!isLast && tw`dark:border-divider border-b border-gray-200`,
|
|
50
|
+
]}
|
|
51
|
+
onPress={onPress}
|
|
52
|
+
>
|
|
53
|
+
<View style={tw`flex-1`}>
|
|
54
|
+
<Typography
|
|
55
|
+
style={[
|
|
56
|
+
tw`text-base`,
|
|
57
|
+
isSelected ? tw`text-primary-500 font-medium` : tw`dark:text-foreground text-gray-800`,
|
|
58
|
+
]}
|
|
59
|
+
>
|
|
60
|
+
{label}
|
|
61
|
+
</Typography>
|
|
62
|
+
</View>
|
|
63
|
+
</Pressable>
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function SelectDropdown(props: Props) {
|
|
68
|
+
const { label, onSelect, options, placeholder = 'Select', selectedId, style } = props;
|
|
69
|
+
const [isOpen, setIsOpen] = useState<boolean>(false);
|
|
70
|
+
const [dropdownLayout, setDropdownLayout] = useState<DropdownLayout | null>(null);
|
|
71
|
+
const triggerRef = useRef<View>(null);
|
|
72
|
+
|
|
73
|
+
const selectedOption = useMemo(
|
|
74
|
+
() => options.find((option) => option.id === selectedId),
|
|
75
|
+
[options, selectedId],
|
|
76
|
+
);
|
|
77
|
+
const selectedLabel = selectedOption?.label ?? placeholder;
|
|
78
|
+
const lastIndex = options.length - 1;
|
|
79
|
+
|
|
80
|
+
const handleToggle = useCallback(() => {
|
|
81
|
+
setIsOpen((prev) => !prev);
|
|
82
|
+
}, []);
|
|
83
|
+
|
|
84
|
+
const handleClose = useCallback(() => {
|
|
85
|
+
setIsOpen(false);
|
|
86
|
+
}, []);
|
|
87
|
+
|
|
88
|
+
const handleSelect = useCallback(
|
|
89
|
+
(optionId: string) => {
|
|
90
|
+
onSelect(optionId);
|
|
91
|
+
setIsOpen(false);
|
|
92
|
+
},
|
|
93
|
+
[onSelect],
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
useEffect(() => {
|
|
97
|
+
if (!isOpen) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
triggerRef.current?.measureInWindow((x, y, width, height) => {
|
|
102
|
+
setDropdownLayout({ height, width, x, y });
|
|
103
|
+
});
|
|
104
|
+
}, [isOpen]);
|
|
105
|
+
|
|
106
|
+
const optionPressHandlers = useMemo(() => {
|
|
107
|
+
const handlers: Record<string, () => void> = {};
|
|
108
|
+
|
|
109
|
+
options.forEach((option) => {
|
|
110
|
+
handlers[option.id] = () => handleSelect(option.id);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
return handlers;
|
|
114
|
+
}, [handleSelect, options]);
|
|
115
|
+
|
|
116
|
+
function renderOption(option: DropdownOption, index: number) {
|
|
117
|
+
const handlePress = optionPressHandlers[option.id];
|
|
118
|
+
|
|
119
|
+
return (
|
|
120
|
+
<OptionItem
|
|
121
|
+
key={option.id}
|
|
122
|
+
isLast={index === lastIndex}
|
|
123
|
+
isSelected={option.id === selectedId}
|
|
124
|
+
label={option.label}
|
|
125
|
+
onPress={handlePress}
|
|
126
|
+
/>
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function renderMenu() {
|
|
131
|
+
if (!dropdownLayout) {
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const screenHeight = Dimensions.get('window').height;
|
|
136
|
+
const estimatedMenuHeight = Math.min(MAX_MENU_HEIGHT, options.length * OPTION_ROW_HEIGHT);
|
|
137
|
+
const spaceBelow = screenHeight - (dropdownLayout.y + dropdownLayout.height) - DROPDOWN_OFFSET;
|
|
138
|
+
const spaceAbove = dropdownLayout.y - DROPDOWN_OFFSET;
|
|
139
|
+
const canOpenBelow = spaceBelow >= estimatedMenuHeight;
|
|
140
|
+
const canOpenAbove = spaceAbove >= estimatedMenuHeight;
|
|
141
|
+
const shouldOpenBelow =
|
|
142
|
+
canOpenBelow || (!canOpenBelow && !canOpenAbove && spaceBelow >= spaceAbove);
|
|
143
|
+
const preferredTop = shouldOpenBelow
|
|
144
|
+
? dropdownLayout.y + dropdownLayout.height + DROPDOWN_OFFSET
|
|
145
|
+
: dropdownLayout.y - estimatedMenuHeight - DROPDOWN_OFFSET;
|
|
146
|
+
const clampedTop = Math.min(
|
|
147
|
+
Math.max(0, preferredTop),
|
|
148
|
+
Math.max(0, screenHeight - estimatedMenuHeight - DROPDOWN_OFFSET),
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
return (
|
|
152
|
+
<View
|
|
153
|
+
style={[
|
|
154
|
+
tw`dark:border-divider dark:bg-sheet absolute z-50 max-h-80 rounded-xl border border-gray-200 bg-white`,
|
|
155
|
+
{
|
|
156
|
+
elevation: 5,
|
|
157
|
+
left: dropdownLayout.x,
|
|
158
|
+
shadowColor: colors.gray[900],
|
|
159
|
+
shadowOffset: {
|
|
160
|
+
height: 2,
|
|
161
|
+
width: 0,
|
|
162
|
+
},
|
|
163
|
+
shadowOpacity: 0.15,
|
|
164
|
+
shadowRadius: 4,
|
|
165
|
+
top: clampedTop,
|
|
166
|
+
width: dropdownLayout.width,
|
|
167
|
+
},
|
|
168
|
+
]}
|
|
169
|
+
>
|
|
170
|
+
<ScrollView showsVerticalScrollIndicator={false} style={tw`max-h-80`}>
|
|
171
|
+
{options.map(renderOption)}
|
|
172
|
+
</ScrollView>
|
|
173
|
+
</View>
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return (
|
|
178
|
+
<>
|
|
179
|
+
{label && (
|
|
180
|
+
<Typography style={tw`mb-1 text-sm font-medium text-gray-700 dark:text-gray-300`}>
|
|
181
|
+
{label}
|
|
182
|
+
</Typography>
|
|
183
|
+
)}
|
|
184
|
+
<Pressable
|
|
185
|
+
ref={triggerRef}
|
|
186
|
+
style={[
|
|
187
|
+
defaultInputContainerStyle,
|
|
188
|
+
tw`dark:border-divider dark:bg-surface items-center justify-between`,
|
|
189
|
+
style,
|
|
190
|
+
]}
|
|
191
|
+
onPress={handleToggle}
|
|
192
|
+
>
|
|
193
|
+
<View style={tw`flex-1`}>
|
|
194
|
+
<Typography
|
|
195
|
+
style={[
|
|
196
|
+
defaultInputTextStyle,
|
|
197
|
+
tw`dark:text-foreground`,
|
|
198
|
+
!selectedOption && tw`dark:text-placeholder text-gray-400`,
|
|
199
|
+
]}
|
|
200
|
+
>
|
|
201
|
+
{selectedLabel}
|
|
202
|
+
</Typography>
|
|
203
|
+
</View>
|
|
204
|
+
{isOpen ? (
|
|
205
|
+
<ChevronDownIcon height={20} style={tw`dark:text-subtitle text-gray-500`} width={20} />
|
|
206
|
+
) : (
|
|
207
|
+
<ChevronRightIcon height={20} style={tw`dark:text-subtitle text-gray-500`} width={20} />
|
|
208
|
+
)}
|
|
209
|
+
</Pressable>
|
|
210
|
+
|
|
211
|
+
<Modal
|
|
212
|
+
containerStyle={tw`flex-1 p-0`}
|
|
213
|
+
isVisible={isOpen}
|
|
214
|
+
onBackButtonPress={handleClose}
|
|
215
|
+
onBackdropPress={handleClose}
|
|
216
|
+
>
|
|
217
|
+
<View pointerEvents="box-none" style={tw`flex-1`}>
|
|
218
|
+
{renderMenu()}
|
|
219
|
+
</View>
|
|
220
|
+
</Modal>
|
|
221
|
+
</>
|
|
222
|
+
);
|
|
223
|
+
}
|
|
@@ -36,7 +36,7 @@ export function Skeleton(props: Props) {
|
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
return (
|
|
39
|
-
<Animated.View style={[tw`rounded-lg bg-gray-200`, animatedStyle, style]}>
|
|
39
|
+
<Animated.View style={[tw`dark:bg-surface rounded-lg bg-gray-200`, animatedStyle, style]}>
|
|
40
40
|
<View style={tw`opacity-0`}>{children}</View>
|
|
41
41
|
</Animated.View>
|
|
42
42
|
);
|
|
@@ -88,6 +88,7 @@ export function BottomSheetTextInput(props: BottomSheetTextInputProps) {
|
|
|
88
88
|
<View
|
|
89
89
|
style={[
|
|
90
90
|
defaultInputContainerStyle,
|
|
91
|
+
tw`dark:border-divider dark:bg-surface`,
|
|
91
92
|
focusedInputStyle(isFocused),
|
|
92
93
|
disabledInputStyle(isDisabled),
|
|
93
94
|
style,
|
|
@@ -100,8 +101,8 @@ export function BottomSheetTextInput(props: BottomSheetTextInputProps) {
|
|
|
100
101
|
multiline={multiline}
|
|
101
102
|
placeholder={placeholder}
|
|
102
103
|
placeholderTextColor={colors.gray[500]}
|
|
103
|
-
selectionColor={colors.primary}
|
|
104
|
-
style={[defaultInputTextStyle, textStyle]}
|
|
104
|
+
selectionColor={colors.primary[400]}
|
|
105
|
+
style={[defaultInputTextStyle, tw`dark:text-foreground`, textStyle]}
|
|
105
106
|
value={value}
|
|
106
107
|
onBlur={(e) => handleOnBlur(e as NativeSyntheticEvent<TextInputFocusEventData>)}
|
|
107
108
|
onChangeText={handleOnChangeText}
|
|
@@ -115,7 +116,7 @@ export function BottomSheetTextInput(props: BottomSheetTextInputProps) {
|
|
|
115
116
|
testID="clear-button"
|
|
116
117
|
onPress={handleOnClearPress}
|
|
117
118
|
>
|
|
118
|
-
<CrossIcon />
|
|
119
|
+
<CrossIcon style={tw`text-subtitle`} />
|
|
119
120
|
</Pressable>
|
|
120
121
|
)}
|
|
121
122
|
</View>
|
package/templates/shared/apps/mobile/src/components/atoms/TextInput/text-input.component.tsx
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
import { DefaultNameInputProps } from './constants';
|
|
12
12
|
|
|
13
13
|
import { CrossIcon } from '@/icons';
|
|
14
|
+
import { useLocalStorageStore } from '@/stores';
|
|
14
15
|
import {
|
|
15
16
|
colors,
|
|
16
17
|
defaultInputContainerStyle,
|
|
@@ -43,6 +44,7 @@ export function TextInput(props: TextInputProps) {
|
|
|
43
44
|
value,
|
|
44
45
|
...extraProps
|
|
45
46
|
} = props;
|
|
47
|
+
const colorScheme = useLocalStorageStore((s) => s.colorScheme);
|
|
46
48
|
const [isClearButtonVisible, setIsClearButtonVisible] = useState<boolean>(false);
|
|
47
49
|
const [isFocused, setFocused] = useState<boolean>(false);
|
|
48
50
|
|
|
@@ -81,6 +83,7 @@ export function TextInput(props: TextInputProps) {
|
|
|
81
83
|
<View
|
|
82
84
|
style={[
|
|
83
85
|
defaultInputContainerStyle,
|
|
86
|
+
tw`dark:border-divider dark:bg-surface`,
|
|
84
87
|
focusedInputStyle(isFocused),
|
|
85
88
|
disabledInputStyle(isDisabled),
|
|
86
89
|
style,
|
|
@@ -89,12 +92,13 @@ export function TextInput(props: TextInputProps) {
|
|
|
89
92
|
<RNTextInput
|
|
90
93
|
{...DefaultNameInputProps}
|
|
91
94
|
ref={textInputRef}
|
|
95
|
+
cursorColor={colors.primary[400]}
|
|
92
96
|
editable={!isDisabled}
|
|
93
97
|
multiline={multiline}
|
|
94
98
|
placeholder={placeholder}
|
|
95
|
-
placeholderTextColor={colors.gray[
|
|
96
|
-
selectionColor={colors.primary}
|
|
97
|
-
style={[defaultInputTextStyle, textStyle]}
|
|
99
|
+
placeholderTextColor={colorScheme === 'dark' ? colors.placeholder : colors.gray[400]}
|
|
100
|
+
selectionColor={colors.primary[400]}
|
|
101
|
+
style={[defaultInputTextStyle, tw`dark:text-foreground`, textStyle]}
|
|
98
102
|
value={value}
|
|
99
103
|
onBlur={handleOnBlur}
|
|
100
104
|
onChangeText={handleOnChangeText}
|
|
@@ -108,7 +112,7 @@ export function TextInput(props: TextInputProps) {
|
|
|
108
112
|
testID="clear-button"
|
|
109
113
|
onPress={handleOnClearPress}
|
|
110
114
|
>
|
|
111
|
-
<CrossIcon style={tw`text-gray-400`} />
|
|
115
|
+
<CrossIcon style={tw`dark:text-subtitle text-gray-400`} />
|
|
112
116
|
</Pressable>
|
|
113
117
|
)}
|
|
114
118
|
</View>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './theme-manager.component';
|