@fadyshawky/react-native-magic 2.2.0 → 2.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/LICENSE +21 -0
- package/README.md +90 -55
- package/index.js +4 -0
- package/package.json +9 -3
- package/scripts/askPackageName.js +10 -5
- package/template/.env.development +8 -6
- package/template/.env.example +15 -5
- package/template/.env.production +8 -6
- package/template/.env.staging +8 -6
- package/template/.eslintrc.js +14 -0
- package/template/.husky/pre-commit +1 -0
- package/template/App.tsx +47 -16
- package/template/__tests__/App.test.tsx +28 -10
- package/template/babel.config.js +20 -1
- package/template/docs/ARCHITECTURE.md +40 -10
- package/template/docs/BEST_PRACTICES.md +10 -1
- package/template/docs/CUSTOMIZATION.md +118 -5
- package/template/docs/design-system.html +1164 -0
- package/template/docs/wireframes.html +411 -0
- package/template/index.js +10 -0
- package/template/jest.config.js +16 -1
- package/template/jest.setup.js +61 -0
- package/template/package-lock.json +12178 -8293
- package/template/package.json +53 -19
- package/template/react-native.config.js +3 -0
- package/template/resources/fonts/.gitkeep +0 -0
- package/template/scripts/ci-sync-env.cjs +71 -0
- package/template/src/assets/brand/logo-mark.svg +8 -0
- package/template/src/assets/brand/logo-mono.svg +9 -0
- package/template/src/assets/brand/logo-primary.svg +15 -0
- package/template/src/assets/brand/wordmark-dark.svg +18 -0
- package/template/src/common/components/AppBottomSheet.tsx +87 -0
- package/template/src/common/components/AppSwitch.tsx +75 -0
- package/template/src/common/components/AppTextInput.tsx +161 -0
- package/template/src/common/components/Avatar.tsx +75 -0
- package/template/src/common/components/Badge.tsx +66 -0
- package/template/src/common/components/CardScroller.tsx +58 -0
- package/template/src/common/components/Cards.tsx +13 -7
- package/template/src/common/components/Carousel.tsx +196 -0
- package/template/src/common/components/Checkbox.tsx +85 -0
- package/template/src/common/components/Chip.tsx +55 -0
- package/template/src/common/components/Dropdown.tsx +202 -0
- package/template/src/common/components/ErrorBoundary.tsx +82 -0
- package/template/src/common/components/FlatListWrapper.tsx +8 -8
- package/template/src/common/components/ListItem.tsx +90 -0
- package/template/src/common/components/LoadingComponent.tsx +8 -2
- package/template/src/common/components/Logo.tsx +77 -0
- package/template/src/common/components/ModalDialog.tsx +141 -0
- package/template/src/common/components/NetworkBanner.tsx +47 -0
- package/template/src/common/components/OTPInput.tsx +0 -1
- package/template/src/common/components/PrimaryButton.tsx +0 -14
- package/template/src/common/components/PrimaryTextInput.tsx +66 -130
- package/template/src/common/components/RadioGroup.tsx +95 -0
- package/template/src/common/components/SafeText.tsx +4 -3
- package/template/src/common/components/SearchBar.tsx +7 -5
- package/template/src/common/components/SegmentedControl.tsx +77 -0
- package/template/src/common/components/Skeleton.tsx +47 -0
- package/template/src/common/components/TryAgain.tsx +4 -2
- package/template/src/common/helpers/arrayHelpers.ts +2 -2
- package/template/src/common/helpers/defaultKeyIdExtractor.ts +1 -1
- package/template/src/common/helpers/regexHelpers.ts +1 -2
- package/template/src/common/helpers/stringsHelpers.ts +0 -1
- package/template/src/common/hooks/useBackHandler.ts +5 -2
- package/template/src/common/hooks/useEventRegister.ts +1 -1
- package/template/src/common/hooks/useFlatListActions.ts +1 -1
- package/template/src/common/hooks/useWhyDidYouUpdate.ts +1 -1
- package/template/src/common/localization/LocalizationProvider.tsx +1 -1
- package/template/src/common/localization/RTLInitializer.tsx +1 -1
- package/template/src/common/localization/dateFormatter.ts +0 -1
- package/template/src/common/localization/intlFormatter.ts +0 -1
- package/template/src/common/localization/localization.ts +2 -2
- package/template/src/common/localization/translations/homeLocalization.ts +14 -0
- package/template/src/common/localization/translations/loginLocalization.ts +8 -0
- package/template/src/common/localization/translations/mainNavigationLocalization.ts +2 -0
- package/template/src/common/localization/translations/profileLocalization.ts +16 -0
- package/template/src/common/utils/index.tsx +0 -6
- package/template/src/common/validations/commonValidations.ts +2 -2
- package/template/src/core/api/errorHandler.ts +1 -1
- package/template/src/core/api/responseHandlers.ts +1 -3
- package/template/src/core/api/serverHeaders.ts +61 -12
- package/template/src/core/notifications/notificationAuth.ts +6 -0
- package/template/src/core/notifications/notificationService.ts +125 -0
- package/template/src/core/notifications/routeFromNotificationData.ts +32 -0
- package/template/src/core/store/categories/categoriesActions.ts +25 -0
- package/template/src/core/store/categories/categoriesSlice.ts +51 -0
- package/template/src/core/store/categories/categoriesState.ts +19 -0
- package/template/src/core/store/rootReducer.ts +2 -0
- package/template/src/core/store/store.tsx +6 -1
- package/template/src/core/store/user/userActions.ts +75 -14
- package/template/src/core/store/user/userSlice.ts +49 -26
- package/template/src/core/store/user/userState.ts +6 -4
- package/template/src/core/theme/ThemeProvider.tsx +5 -3
- package/template/src/core/theme/brand.ts +50 -0
- package/template/src/core/theme/colors.ts +113 -99
- package/template/src/core/theme/commonConsts.ts +2 -2
- package/template/src/core/theme/commonStyles.ts +1 -1
- package/template/src/core/theme/themes.ts +2 -0
- package/template/src/core/theme/types.ts +4 -2
- package/template/src/core/utils/stringUtils.ts +1 -1
- package/template/src/design-system/index.ts +2 -0
- package/template/src/design-system/tokens/brand.ts +6 -0
- package/template/src/design-system/tokens/index.ts +3 -0
- package/template/src/design-system/tokens/palette.ts +4 -0
- package/template/src/design-system/tokens/typography-spacing.ts +2 -0
- package/template/src/navigation/AuthStack.tsx +1 -4
- package/template/src/navigation/HeaderComponents.tsx +6 -3
- package/template/src/navigation/MainStack.tsx +18 -6
- package/template/src/navigation/RootNavigation.tsx +4 -7
- package/template/src/navigation/TabBar.tsx +7 -6
- package/template/src/navigation/types.ts +10 -31
- package/template/src/screens/Login/Login.tsx +47 -47
- package/template/src/screens/OTP/OTPScreen.tsx +6 -9
- package/template/src/screens/components/ComponentsScreen.tsx +301 -0
- package/template/src/screens/home/HomeScreen.tsx +143 -1
- package/template/src/screens/home/hooks/useHomeData.ts +19 -5
- package/template/src/screens/index.tsx +1 -0
- package/template/src/screens/profile/Profile.tsx +139 -2
- package/template/src/screens/splash/Splash.tsx +44 -11
- package/template/src/sheetManager/sheets.tsx +1 -1
- package/template/tsconfig.json +14 -2
- package/template/types/globals.d.ts +43 -0
- package/template/types/index.ts +2 -6
- package/template/types/modules.d.ts +9 -0
- package/template/types/react-native-config.d.ts +0 -2
- package/.vscode/settings.json +0 -8
- package/CHANGELOG.md +0 -119
- package/CODE_OF_CONDUCT.md +0 -83
- package/CONTRIBUTING.md +0 -60
- package/local.properties +0 -1
- package/template/src/common/components/ImageCropPickerButton.tsx +0 -107
- package/template/src/common/components/PhotoTakingButton.tsx +0 -94
- package/template/src/common/helpers/imageHelpers.ts +0 -5
- package/template/src/common/helpers/inAppReviewHelper.ts +0 -30
- package/template/src/common/helpers/orientationHelpers.ts +0 -25
- package/template/src/common/helpers/shareHelpers.ts +0 -47
- package/template/src/common/utils/FeesCaalculation.tsx +0 -37
- package/template/src/common/utils/printData.tsx +0 -161
- package/template/src/common/validations/examples/TextInputWithValidation.tsx +0 -229
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import {StyleSheet, View} from 'react-native';
|
|
3
|
+
import {CommonSizes} from '../../core/theme/commonSizes';
|
|
4
|
+
import {useTheme} from '../../core/theme/ThemeProvider';
|
|
5
|
+
import {RTLAwareText} from './RTLAwareText';
|
|
6
|
+
|
|
7
|
+
type BadgeVariant = 'primary' | 'success' | 'error' | 'neutral';
|
|
8
|
+
|
|
9
|
+
interface BadgeProps {
|
|
10
|
+
label?: string;
|
|
11
|
+
count?: number;
|
|
12
|
+
variant?: BadgeVariant;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* A small pill for counts or short labels. Colored variants (primary / success
|
|
17
|
+
* / error) use white text; the neutral variant uses a subtle surface with the
|
|
18
|
+
* default text color. A `count` wins over `label` and is capped at "99+".
|
|
19
|
+
*/
|
|
20
|
+
export function Badge({
|
|
21
|
+
label,
|
|
22
|
+
count,
|
|
23
|
+
variant = 'primary',
|
|
24
|
+
}: BadgeProps): JSX.Element {
|
|
25
|
+
const {theme} = useTheme();
|
|
26
|
+
|
|
27
|
+
const backgroundColor =
|
|
28
|
+
variant === 'success'
|
|
29
|
+
? theme.colors.success_400
|
|
30
|
+
: variant === 'error'
|
|
31
|
+
? theme.colors.error_400
|
|
32
|
+
: variant === 'neutral'
|
|
33
|
+
? theme.colors.grayScale_50
|
|
34
|
+
: theme.colors.PlatinateBlue_400;
|
|
35
|
+
|
|
36
|
+
const isNeutral = variant === 'neutral';
|
|
37
|
+
|
|
38
|
+
const content =
|
|
39
|
+
count != null ? (count > 99 ? '99+' : String(count)) : label;
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<View style={[styles.badge, {backgroundColor}]}>
|
|
43
|
+
<RTLAwareText
|
|
44
|
+
style={
|
|
45
|
+
isNeutral
|
|
46
|
+
? theme.text.bodySmallBold
|
|
47
|
+
: [theme.text.bodySmallBold, styles.labelOnColor]
|
|
48
|
+
}>
|
|
49
|
+
{content}
|
|
50
|
+
</RTLAwareText>
|
|
51
|
+
</View>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const styles = StyleSheet.create({
|
|
56
|
+
badge: {
|
|
57
|
+
alignSelf: 'flex-start',
|
|
58
|
+
alignItems: 'center',
|
|
59
|
+
justifyContent: 'center',
|
|
60
|
+
borderRadius: CommonSizes.borderRadius.full,
|
|
61
|
+
paddingHorizontal: CommonSizes.spacing.small,
|
|
62
|
+
},
|
|
63
|
+
labelOnColor: {
|
|
64
|
+
color: '#FFFFFF',
|
|
65
|
+
},
|
|
66
|
+
});
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import React, {useCallback} from 'react';
|
|
2
|
+
import {FlatList, StyleSheet, View} from 'react-native';
|
|
3
|
+
import {CommonSizes} from '../../core/theme/commonSizes';
|
|
4
|
+
|
|
5
|
+
interface CardScrollerProps<T> {
|
|
6
|
+
data: T[];
|
|
7
|
+
renderItem: (info: {item: T; index: number}) => React.ReactElement;
|
|
8
|
+
cardWidth?: number;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const DEFAULT_CARD_WIDTH = 260;
|
|
12
|
+
const CARD_GAP = CommonSizes.spacing.medium;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* A horizontal, snap-to-card scroller. Cards are a fixed width with a uniform
|
|
16
|
+
* gap between them and inset content padding. Fast deceleration + snapping
|
|
17
|
+
* give a premium "shelf of cards" feel.
|
|
18
|
+
*/
|
|
19
|
+
export function CardScroller<T>({
|
|
20
|
+
data,
|
|
21
|
+
renderItem,
|
|
22
|
+
cardWidth = DEFAULT_CARD_WIDTH,
|
|
23
|
+
}: CardScrollerProps<T>): JSX.Element {
|
|
24
|
+
const renderCard = useCallback(
|
|
25
|
+
({item, index}: {item: T; index: number}) => (
|
|
26
|
+
<View style={{width: cardWidth}}>{renderItem({item, index})}</View>
|
|
27
|
+
),
|
|
28
|
+
[renderItem, cardWidth],
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<FlatList
|
|
33
|
+
data={data}
|
|
34
|
+
renderItem={renderCard}
|
|
35
|
+
keyExtractor={(_, index) => `card-${index}`}
|
|
36
|
+
horizontal
|
|
37
|
+
showsHorizontalScrollIndicator={false}
|
|
38
|
+
snapToInterval={cardWidth + CARD_GAP}
|
|
39
|
+
snapToAlignment="start"
|
|
40
|
+
decelerationRate="fast"
|
|
41
|
+
ItemSeparatorComponent={ItemSeparator}
|
|
42
|
+
contentContainerStyle={styles.content}
|
|
43
|
+
/>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function ItemSeparator(): JSX.Element {
|
|
48
|
+
return <View style={styles.separator} />;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const styles = StyleSheet.create({
|
|
52
|
+
content: {
|
|
53
|
+
paddingHorizontal: CommonSizes.spacing.large,
|
|
54
|
+
},
|
|
55
|
+
separator: {
|
|
56
|
+
width: CARD_GAP,
|
|
57
|
+
},
|
|
58
|
+
});
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import {
|
|
3
|
-
Image,
|
|
4
3
|
StyleSheet,
|
|
5
4
|
Text,
|
|
6
5
|
TouchableOpacity,
|
|
@@ -36,7 +35,7 @@ export const Card = ({
|
|
|
36
35
|
{backgroundColor: theme.colors.surface},
|
|
37
36
|
createThemedStyles(theme).dropShadow,
|
|
38
37
|
{marginRight: marginRight},
|
|
39
|
-
|
|
38
|
+
styles.cardOverflowHidden,
|
|
40
39
|
cardStyle,
|
|
41
40
|
]}
|
|
42
41
|
onPress={() => {
|
|
@@ -46,15 +45,12 @@ export const Card = ({
|
|
|
46
45
|
{icon && icon.uri && (
|
|
47
46
|
<FastImage
|
|
48
47
|
source={{uri: icon.uri, cache: FastImage.cacheControl.immutable}}
|
|
49
|
-
style={
|
|
50
|
-
width: '100%',
|
|
51
|
-
height: '100%',
|
|
52
|
-
}}
|
|
48
|
+
style={styles.fastImage}
|
|
53
49
|
resizeMode={FastImage.resizeMode.contain}
|
|
54
50
|
/>
|
|
55
51
|
)}
|
|
56
52
|
</View>
|
|
57
|
-
<Text style={[theme.text.cards,
|
|
53
|
+
<Text style={[theme.text.cards, styles.titleCentered]}>{title}</Text>
|
|
58
54
|
</TouchableOpacity>
|
|
59
55
|
);
|
|
60
56
|
};
|
|
@@ -69,6 +65,16 @@ const styles = StyleSheet.create({
|
|
|
69
65
|
width: scaleWidth(187),
|
|
70
66
|
height: scaleHeight(280),
|
|
71
67
|
},
|
|
68
|
+
cardOverflowHidden: {
|
|
69
|
+
overflow: 'hidden',
|
|
70
|
+
},
|
|
71
|
+
fastImage: {
|
|
72
|
+
width: '100%',
|
|
73
|
+
height: '100%',
|
|
74
|
+
},
|
|
75
|
+
titleCentered: {
|
|
76
|
+
textAlign: 'center',
|
|
77
|
+
},
|
|
72
78
|
blurContainer: {
|
|
73
79
|
flex: 1,
|
|
74
80
|
padding: CommonSizes.spacing.medium,
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import React, {useCallback, useRef, useState} from 'react';
|
|
2
|
+
import {
|
|
3
|
+
Dimensions,
|
|
4
|
+
FlatList,
|
|
5
|
+
NativeScrollEvent,
|
|
6
|
+
NativeSyntheticEvent,
|
|
7
|
+
Pressable,
|
|
8
|
+
StyleSheet,
|
|
9
|
+
View,
|
|
10
|
+
} from 'react-native';
|
|
11
|
+
import Svg, {Path} from 'react-native-svg';
|
|
12
|
+
import {CommonSizes} from '../../core/theme/commonSizes';
|
|
13
|
+
import {useTheme} from '../../core/theme/ThemeProvider';
|
|
14
|
+
|
|
15
|
+
interface CarouselProps<T> {
|
|
16
|
+
data: T[];
|
|
17
|
+
renderItem: (info: {item: T; index: number}) => React.ReactElement;
|
|
18
|
+
height?: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const ARROW = 32;
|
|
22
|
+
const GAP = CommonSizes.spacing.medium;
|
|
23
|
+
const CONTENT_WIDTH =
|
|
24
|
+
Dimensions.get('window').width - 2 * CommonSizes.spacing.large;
|
|
25
|
+
// Card width = content minus the two arrow gutters, so arrows sit BESIDE the
|
|
26
|
+
// card (in the side gutters), never overlapping it.
|
|
27
|
+
const ITEM_WIDTH = CONTENT_WIDTH - 2 * (ARROW + GAP);
|
|
28
|
+
|
|
29
|
+
function Chevron({dir, color}: {dir: 'left' | 'right'; color: string}) {
|
|
30
|
+
const d = dir === 'left' ? 'M15 5 L8 12 L15 19' : 'M9 5 L16 12 L9 19';
|
|
31
|
+
return (
|
|
32
|
+
<Svg width={18} height={22} viewBox="0 0 24 24" fill="none">
|
|
33
|
+
<Path
|
|
34
|
+
d={d}
|
|
35
|
+
stroke={color}
|
|
36
|
+
strokeWidth={2.5}
|
|
37
|
+
strokeLinecap="round"
|
|
38
|
+
strokeLinejoin="round"
|
|
39
|
+
/>
|
|
40
|
+
</Svg>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Horizontal paging carousel with the card flanked by prev/next arrow buttons
|
|
46
|
+
* in the side gutters (never over the card), plus pagination dots below.
|
|
47
|
+
* Active dot is wide + primary blue; the rest are small + muted.
|
|
48
|
+
*/
|
|
49
|
+
export function Carousel<T>({
|
|
50
|
+
data,
|
|
51
|
+
renderItem,
|
|
52
|
+
height,
|
|
53
|
+
}: CarouselProps<T>): JSX.Element {
|
|
54
|
+
const {theme} = useTheme();
|
|
55
|
+
const listRef = useRef<FlatList<T>>(null);
|
|
56
|
+
const [activeIndex, setActiveIndex] = useState(0);
|
|
57
|
+
const lastIndex = data.length - 1;
|
|
58
|
+
|
|
59
|
+
const goTo = useCallback(
|
|
60
|
+
(index: number) => {
|
|
61
|
+
const next = Math.max(0, Math.min(index, data.length - 1));
|
|
62
|
+
listRef.current?.scrollToOffset({
|
|
63
|
+
offset: next * ITEM_WIDTH,
|
|
64
|
+
animated: true,
|
|
65
|
+
});
|
|
66
|
+
setActiveIndex(next);
|
|
67
|
+
},
|
|
68
|
+
[data.length],
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
const onMomentumScrollEnd = useCallback(
|
|
72
|
+
(event: NativeSyntheticEvent<NativeScrollEvent>) => {
|
|
73
|
+
const offsetX = event.nativeEvent.contentOffset.x;
|
|
74
|
+
setActiveIndex(Math.round(offsetX / ITEM_WIDTH));
|
|
75
|
+
},
|
|
76
|
+
[],
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
const renderCarouselItem = useCallback(
|
|
80
|
+
({item, index}: {item: T; index: number}) => (
|
|
81
|
+
<View style={[styles.itemContainer, height != null ? {height} : null]}>
|
|
82
|
+
{renderItem({item, index})}
|
|
83
|
+
</View>
|
|
84
|
+
),
|
|
85
|
+
[renderItem, height],
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
const prevDisabled = activeIndex <= 0;
|
|
89
|
+
const nextDisabled = activeIndex >= lastIndex;
|
|
90
|
+
const arrowColor = theme.colors.PlatinateBlue_400;
|
|
91
|
+
const disabledColor = theme.colors.grayScale_100;
|
|
92
|
+
|
|
93
|
+
const prevArrowStyle = {
|
|
94
|
+
backgroundColor: theme.colors.grayScale_0,
|
|
95
|
+
borderColor: theme.colors.grayScale_50,
|
|
96
|
+
opacity: prevDisabled ? 0.4 : 1,
|
|
97
|
+
};
|
|
98
|
+
const nextArrowStyle = {
|
|
99
|
+
backgroundColor: theme.colors.grayScale_0,
|
|
100
|
+
borderColor: theme.colors.grayScale_50,
|
|
101
|
+
opacity: nextDisabled ? 0.4 : 1,
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
return (
|
|
105
|
+
<View>
|
|
106
|
+
<View style={styles.row}>
|
|
107
|
+
<Pressable
|
|
108
|
+
onPress={() => goTo(activeIndex - 1)}
|
|
109
|
+
disabled={prevDisabled}
|
|
110
|
+
hitSlop={8}
|
|
111
|
+
accessibilityRole="button"
|
|
112
|
+
accessibilityLabel="Previous"
|
|
113
|
+
style={[styles.arrow, prevArrowStyle]}>
|
|
114
|
+
<Chevron dir="left" color={prevDisabled ? disabledColor : arrowColor} />
|
|
115
|
+
</Pressable>
|
|
116
|
+
|
|
117
|
+
<FlatList
|
|
118
|
+
ref={listRef}
|
|
119
|
+
data={data}
|
|
120
|
+
renderItem={renderCarouselItem}
|
|
121
|
+
keyExtractor={(_, index) => `carousel-${index}`}
|
|
122
|
+
horizontal
|
|
123
|
+
pagingEnabled
|
|
124
|
+
showsHorizontalScrollIndicator={false}
|
|
125
|
+
snapToInterval={ITEM_WIDTH}
|
|
126
|
+
snapToAlignment="start"
|
|
127
|
+
decelerationRate="fast"
|
|
128
|
+
disableIntervalMomentum
|
|
129
|
+
onMomentumScrollEnd={onMomentumScrollEnd}
|
|
130
|
+
style={styles.list}
|
|
131
|
+
/>
|
|
132
|
+
|
|
133
|
+
<Pressable
|
|
134
|
+
onPress={() => goTo(activeIndex + 1)}
|
|
135
|
+
disabled={nextDisabled}
|
|
136
|
+
hitSlop={8}
|
|
137
|
+
accessibilityRole="button"
|
|
138
|
+
accessibilityLabel="Next"
|
|
139
|
+
style={[styles.arrow, nextArrowStyle]}>
|
|
140
|
+
<Chevron
|
|
141
|
+
dir="right"
|
|
142
|
+
color={nextDisabled ? disabledColor : arrowColor}
|
|
143
|
+
/>
|
|
144
|
+
</Pressable>
|
|
145
|
+
</View>
|
|
146
|
+
|
|
147
|
+
<View style={styles.dotsRow}>
|
|
148
|
+
{data.map((_, index) => {
|
|
149
|
+
const isActive = index === activeIndex;
|
|
150
|
+
return (
|
|
151
|
+
<View
|
|
152
|
+
key={`dot-${index}`}
|
|
153
|
+
style={[
|
|
154
|
+
styles.dot,
|
|
155
|
+
isActive ? styles.dotActive : styles.dotInactive,
|
|
156
|
+
{
|
|
157
|
+
backgroundColor: isActive
|
|
158
|
+
? theme.colors.PlatinateBlue_400
|
|
159
|
+
: theme.colors.grayScale_50,
|
|
160
|
+
},
|
|
161
|
+
]}
|
|
162
|
+
/>
|
|
163
|
+
);
|
|
164
|
+
})}
|
|
165
|
+
</View>
|
|
166
|
+
</View>
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const styles = StyleSheet.create({
|
|
171
|
+
row: {flexDirection: 'row', alignItems: 'center'},
|
|
172
|
+
list: {width: ITEM_WIDTH, flexGrow: 0},
|
|
173
|
+
itemContainer: {width: ITEM_WIDTH},
|
|
174
|
+
arrow: {
|
|
175
|
+
width: ARROW,
|
|
176
|
+
height: ARROW,
|
|
177
|
+
borderRadius: CommonSizes.borderRadius.full,
|
|
178
|
+
borderWidth: CommonSizes.borderWidth.small,
|
|
179
|
+
alignItems: 'center',
|
|
180
|
+
justifyContent: 'center',
|
|
181
|
+
marginHorizontal: GAP / 2,
|
|
182
|
+
},
|
|
183
|
+
dotsRow: {
|
|
184
|
+
flexDirection: 'row',
|
|
185
|
+
alignItems: 'center',
|
|
186
|
+
justifyContent: 'center',
|
|
187
|
+
marginTop: CommonSizes.spacing.large,
|
|
188
|
+
},
|
|
189
|
+
dot: {
|
|
190
|
+
height: 6,
|
|
191
|
+
borderRadius: CommonSizes.borderRadius.full,
|
|
192
|
+
marginHorizontal: CommonSizes.spacing.small,
|
|
193
|
+
},
|
|
194
|
+
dotActive: {width: 18},
|
|
195
|
+
dotInactive: {width: 6},
|
|
196
|
+
});
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import {Pressable, StyleSheet, View, ViewStyle} from 'react-native';
|
|
3
|
+
import Svg, {Path} from 'react-native-svg';
|
|
4
|
+
import {useTheme} from '../../core/theme/ThemeProvider';
|
|
5
|
+
import {CommonSizes} from '../../core/theme/commonSizes';
|
|
6
|
+
import {RTLAwareText} from './RTLAwareText';
|
|
7
|
+
import {RTLAwareView} from './RTLAwareView';
|
|
8
|
+
|
|
9
|
+
interface CheckboxProps {
|
|
10
|
+
checked: boolean;
|
|
11
|
+
onChange: (next: boolean) => void;
|
|
12
|
+
label?: string;
|
|
13
|
+
disabled?: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function CheckMark({color}: {color: string}): JSX.Element {
|
|
17
|
+
return (
|
|
18
|
+
<Svg width={14} height={14} viewBox="0 0 24 24" fill="none">
|
|
19
|
+
<Path
|
|
20
|
+
d="M5 12.5L10 17.5L19 7"
|
|
21
|
+
stroke={color}
|
|
22
|
+
strokeWidth={2.6}
|
|
23
|
+
strokeLinecap="round"
|
|
24
|
+
strokeLinejoin="round"
|
|
25
|
+
/>
|
|
26
|
+
</Svg>
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function Checkbox(props: CheckboxProps): JSX.Element {
|
|
31
|
+
const {checked, onChange, label, disabled} = props;
|
|
32
|
+
const {theme} = useTheme();
|
|
33
|
+
|
|
34
|
+
const boxStyle: ViewStyle = {
|
|
35
|
+
width: 22,
|
|
36
|
+
height: 22,
|
|
37
|
+
borderRadius: CommonSizes.borderRadius.small,
|
|
38
|
+
alignItems: 'center',
|
|
39
|
+
justifyContent: 'center',
|
|
40
|
+
backgroundColor: checked ? theme.colors.PlatinateBlue_400 : 'transparent',
|
|
41
|
+
borderWidth: CommonSizes.borderWidth.medium,
|
|
42
|
+
borderColor: checked
|
|
43
|
+
? theme.colors.PlatinateBlue_400
|
|
44
|
+
: theme.colors.grayScale_50,
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const boxOpacity = {opacity: disabled ? 0.5 : 1};
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<Pressable
|
|
51
|
+
onPress={() => !disabled && onChange(!checked)}
|
|
52
|
+
disabled={disabled}
|
|
53
|
+
hitSlop={8}
|
|
54
|
+
accessibilityRole="checkbox"
|
|
55
|
+
accessibilityState={{checked, disabled}}
|
|
56
|
+
style={boxOpacity}>
|
|
57
|
+
<RTLAwareView style={styles.row}>
|
|
58
|
+
<View style={boxStyle}>
|
|
59
|
+
{checked ? <CheckMark color={'#FFFFFF'} /> : null}
|
|
60
|
+
</View>
|
|
61
|
+
{label ? (
|
|
62
|
+
<RTLAwareText
|
|
63
|
+
style={[
|
|
64
|
+
theme.text.bodyLargeRegular,
|
|
65
|
+
styles.label,
|
|
66
|
+
{color: theme.colors.grayScale_700},
|
|
67
|
+
]}>
|
|
68
|
+
{label}
|
|
69
|
+
</RTLAwareText>
|
|
70
|
+
) : null}
|
|
71
|
+
</RTLAwareView>
|
|
72
|
+
</Pressable>
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const styles = StyleSheet.create({
|
|
77
|
+
row: {
|
|
78
|
+
flexDirection: 'row',
|
|
79
|
+
alignItems: 'center',
|
|
80
|
+
} as ViewStyle,
|
|
81
|
+
label: {
|
|
82
|
+
marginStart: CommonSizes.spacing.large,
|
|
83
|
+
flexShrink: 1,
|
|
84
|
+
},
|
|
85
|
+
});
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import {Pressable, StyleSheet} from 'react-native';
|
|
3
|
+
import {CommonSizes} from '../../core/theme/commonSizes';
|
|
4
|
+
import {useTheme} from '../../core/theme/ThemeProvider';
|
|
5
|
+
import {RTLAwareText} from './RTLAwareText';
|
|
6
|
+
|
|
7
|
+
interface ChipProps {
|
|
8
|
+
label: string;
|
|
9
|
+
selected?: boolean;
|
|
10
|
+
onPress?: () => void;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* A pill-shaped, tappable chip. Selected → solid primary blue with white
|
|
15
|
+
* label; unselected → white surface with a subtle border and default label.
|
|
16
|
+
*/
|
|
17
|
+
export function Chip({label, selected, onPress}: ChipProps): JSX.Element {
|
|
18
|
+
const {theme} = useTheme();
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<Pressable
|
|
22
|
+
onPress={onPress}
|
|
23
|
+
style={[
|
|
24
|
+
styles.chip,
|
|
25
|
+
selected
|
|
26
|
+
? {backgroundColor: theme.colors.PlatinateBlue_400}
|
|
27
|
+
: {
|
|
28
|
+
backgroundColor: theme.colors.grayScale_0,
|
|
29
|
+
borderColor: theme.colors.grayScale_50,
|
|
30
|
+
borderWidth: CommonSizes.borderWidth.small,
|
|
31
|
+
},
|
|
32
|
+
]}>
|
|
33
|
+
<RTLAwareText
|
|
34
|
+
style={
|
|
35
|
+
selected
|
|
36
|
+
? [theme.text.bodyMediumBold, styles.labelSelected]
|
|
37
|
+
: theme.text.bodyMediumBold
|
|
38
|
+
}>
|
|
39
|
+
{label}
|
|
40
|
+
</RTLAwareText>
|
|
41
|
+
</Pressable>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const styles = StyleSheet.create({
|
|
46
|
+
chip: {
|
|
47
|
+
alignSelf: 'flex-start',
|
|
48
|
+
borderRadius: CommonSizes.borderRadius.full,
|
|
49
|
+
paddingHorizontal: CommonSizes.spacing.large,
|
|
50
|
+
paddingVertical: CommonSizes.spacing.small,
|
|
51
|
+
},
|
|
52
|
+
labelSelected: {
|
|
53
|
+
color: '#FFFFFF',
|
|
54
|
+
},
|
|
55
|
+
});
|