@umituz/react-native-design-system 4.28.12 → 4.28.14
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 +1 -1
- package/src/atoms/AtomicDatePicker.tsx +6 -6
- package/src/molecules/StepHeader/StepHeader.tsx +46 -47
- package/src/molecules/StepProgress/StepProgress.tsx +18 -19
- package/src/molecules/action-footer/ActionFooter.tsx +10 -10
- package/src/molecules/alerts/AlertModal.tsx +25 -19
- package/src/molecules/bottom-sheet/components/BottomSheet.tsx +8 -4
- package/src/molecules/bottom-sheet/components/BottomSheetModal.tsx +11 -10
- package/src/molecules/circular-menu/CircularMenuItem.tsx +17 -22
- package/src/molecules/countdown/components/CountdownHeader.tsx +23 -11
- package/src/molecules/countdown/components/TimeUnit.tsx +27 -22
- package/src/molecules/hero-section/HeroSection.tsx +14 -6
- package/src/molecules/icon-grid/IconGrid.tsx +14 -11
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-design-system",
|
|
3
|
-
"version": "4.28.
|
|
3
|
+
"version": "4.28.14",
|
|
4
4
|
"description": "Universal design system for React Native apps with safe navigation hooks - updated SKILL.md with navigation documentation",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* @module AtomicDatePicker
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
-
import React, { useState } from 'react';
|
|
14
|
+
import React, { useState, useCallback, useMemo } from 'react';
|
|
15
15
|
import {
|
|
16
16
|
View,
|
|
17
17
|
Platform,
|
|
@@ -82,7 +82,7 @@ export const AtomicDatePicker: React.FC<AtomicDatePickerProps> = ({
|
|
|
82
82
|
const tokens = useAppDesignTokens();
|
|
83
83
|
const [showPicker, setShowPicker] = useState(false);
|
|
84
84
|
|
|
85
|
-
const handleChange = (event: DateTimePickerEvent, selectedDate?: Date) => {
|
|
85
|
+
const handleChange = useCallback((event: DateTimePickerEvent, selectedDate?: Date) => {
|
|
86
86
|
if (Platform.OS === 'android') {
|
|
87
87
|
setShowPicker(false);
|
|
88
88
|
if (event.type === 'set' && selectedDate) {
|
|
@@ -96,9 +96,9 @@ export const AtomicDatePicker: React.FC<AtomicDatePickerProps> = ({
|
|
|
96
96
|
setShowPicker(false);
|
|
97
97
|
}
|
|
98
98
|
}
|
|
99
|
-
};
|
|
99
|
+
}, [onChange]);
|
|
100
100
|
|
|
101
|
-
const handleOpen = () => {
|
|
101
|
+
const handleOpen = useCallback(() => {
|
|
102
102
|
if (!DateTimePicker) {
|
|
103
103
|
console.warn(
|
|
104
104
|
'[AtomicDatePicker] @react-native-community/datetimepicker is not installed. ' +
|
|
@@ -107,10 +107,10 @@ export const AtomicDatePicker: React.FC<AtomicDatePickerProps> = ({
|
|
|
107
107
|
return;
|
|
108
108
|
}
|
|
109
109
|
setShowPicker(true);
|
|
110
|
-
};
|
|
110
|
+
}, [DateTimePicker]);
|
|
111
111
|
|
|
112
112
|
const { displayText } = useDatePickerText({ value, placeholder, mode });
|
|
113
|
-
const styles = getDatePickerStyles(tokens);
|
|
113
|
+
const styles = useMemo(() => getDatePickerStyles(tokens), [tokens]);
|
|
114
114
|
|
|
115
115
|
return (
|
|
116
116
|
<View style={[styles.container, style]} testID={testID}>
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import React, { useMemo } from "react";
|
|
9
|
-
import { View,
|
|
9
|
+
import { View, type ViewStyle, type StyleProp } from "react-native";
|
|
10
10
|
import { AtomicText } from "../../atoms/AtomicText";
|
|
11
11
|
import { useAppDesignTokens } from "../../theme/hooks/useAppDesignTokens";
|
|
12
12
|
import {
|
|
@@ -60,52 +60,51 @@ export const StepHeader: React.FC<StepHeaderProps> = ({
|
|
|
60
60
|
const spacingMultiplier = tokens.spacingMultiplier;
|
|
61
61
|
|
|
62
62
|
const styles = useMemo(
|
|
63
|
-
() =>
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
paddingHorizontal
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
marginBottom
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
}),
|
|
63
|
+
() => ({
|
|
64
|
+
container: {
|
|
65
|
+
paddingHorizontal: calculateResponsiveSize(
|
|
66
|
+
cfg.spacing?.paddingHorizontal ?? SPACING.xl,
|
|
67
|
+
spacingMultiplier
|
|
68
|
+
),
|
|
69
|
+
marginBottom: calculateResponsiveSize(
|
|
70
|
+
cfg.spacing?.marginBottom ?? 32,
|
|
71
|
+
spacingMultiplier
|
|
72
|
+
),
|
|
73
|
+
},
|
|
74
|
+
stepIndicator: {
|
|
75
|
+
flexDirection: "row" as const,
|
|
76
|
+
alignItems: "center" as const,
|
|
77
|
+
marginBottom: tokens.spacing.md,
|
|
78
|
+
},
|
|
79
|
+
stepDot: {
|
|
80
|
+
width: calculateResponsiveSize(STEP_INDICATOR.dot.width, spacingMultiplier),
|
|
81
|
+
height: calculateResponsiveSize(STEP_INDICATOR.dot.height, spacingMultiplier),
|
|
82
|
+
borderRadius: calculateResponsiveSize(STEP_INDICATOR.dot.borderRadius, spacingMultiplier),
|
|
83
|
+
marginHorizontal: calculateResponsiveSize(STEP_INDICATOR.dot.marginHorizontal, spacingMultiplier),
|
|
84
|
+
},
|
|
85
|
+
activeDot: {
|
|
86
|
+
backgroundColor: tokens.colors.primary,
|
|
87
|
+
},
|
|
88
|
+
inactiveDot: {
|
|
89
|
+
backgroundColor: `${tokens.colors.primary}30`,
|
|
90
|
+
},
|
|
91
|
+
title: {
|
|
92
|
+
fontSize: calculateResponsiveSize(cfg.titleFontSize ?? STEP_INDICATOR.title, spacingMultiplier),
|
|
93
|
+
fontWeight: "900" as const,
|
|
94
|
+
color: tokens.colors.textPrimary,
|
|
95
|
+
textAlign: cfg.titleAlignment,
|
|
96
|
+
marginBottom: subtitle ? calculateResponsiveSize(STEP_INDICATOR.subtitle, spacingMultiplier) : 0,
|
|
97
|
+
letterSpacing: 0.3,
|
|
98
|
+
},
|
|
99
|
+
subtitle: {
|
|
100
|
+
fontSize: calculateResponsiveSize(cfg.subtitleFontSize ?? STEP_INDICATOR.subtitle, spacingMultiplier),
|
|
101
|
+
fontWeight: "500" as const,
|
|
102
|
+
color: tokens.colors.textSecondary,
|
|
103
|
+
textAlign: cfg.titleAlignment,
|
|
104
|
+
lineHeight: calculateLineHeight(cfg.subtitleFontSize ?? STEP_INDICATOR.subtitle, spacingMultiplier),
|
|
105
|
+
opacity: 0.9,
|
|
106
|
+
},
|
|
107
|
+
}),
|
|
109
108
|
[tokens, cfg, subtitle, spacingMultiplier],
|
|
110
109
|
);
|
|
111
110
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { useMemo } from "react";
|
|
2
|
-
import { View,
|
|
2
|
+
import { View, ViewStyle } from "react-native";
|
|
3
3
|
import { useAppDesignTokens } from '../../theme/hooks/useAppDesignTokens';
|
|
4
4
|
import { calculateResponsiveSize } from '../../responsive';
|
|
5
5
|
import { STEP_INDICATOR } from '../../constants';
|
|
@@ -20,24 +20,23 @@ export const StepProgress: React.FC<StepProgressProps> = ({
|
|
|
20
20
|
const spacingMultiplier = tokens.spacingMultiplier;
|
|
21
21
|
|
|
22
22
|
const styles = useMemo(
|
|
23
|
-
() =>
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
}),
|
|
23
|
+
() => ({
|
|
24
|
+
container: {
|
|
25
|
+
flexDirection: "row" as const,
|
|
26
|
+
gap: tokens.spacing.sm,
|
|
27
|
+
paddingHorizontal: tokens.spacing.md,
|
|
28
|
+
paddingVertical: tokens.spacing.md,
|
|
29
|
+
},
|
|
30
|
+
step: {
|
|
31
|
+
flex: 1,
|
|
32
|
+
height: calculateResponsiveSize(STEP_INDICATOR.progressBar.height, spacingMultiplier),
|
|
33
|
+
borderRadius: calculateResponsiveSize(STEP_INDICATOR.progressBar.borderRadius, spacingMultiplier),
|
|
34
|
+
backgroundColor: tokens.colors.border,
|
|
35
|
+
},
|
|
36
|
+
activeStep: {
|
|
37
|
+
backgroundColor: tokens.colors.primary,
|
|
38
|
+
},
|
|
39
|
+
}),
|
|
41
40
|
[tokens, spacingMultiplier],
|
|
42
41
|
);
|
|
43
42
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
|
|
2
2
|
import React, { useMemo, useCallback } from 'react';
|
|
3
|
-
import { View,
|
|
3
|
+
import { View, TouchableOpacity } from 'react-native';
|
|
4
4
|
import { AtomicText } from '../../atoms/AtomicText';
|
|
5
5
|
import { AtomicIcon } from '../../atoms';
|
|
6
6
|
import { useAppDesignTokens } from '../../theme';
|
|
@@ -8,10 +8,10 @@ import type { ActionFooterProps } from './types';
|
|
|
8
8
|
import { calculateResponsiveSize } from '../../responsive';
|
|
9
9
|
import { NAVIGATION } from '../../constants';
|
|
10
10
|
|
|
11
|
-
const createStyles = (spacingMultiplier: number) =>
|
|
11
|
+
const createStyles = (spacingMultiplier: number) => ({
|
|
12
12
|
container: {
|
|
13
|
-
flexDirection: 'row',
|
|
14
|
-
alignItems: 'center',
|
|
13
|
+
flexDirection: 'row' as const,
|
|
14
|
+
alignItems: 'center' as const,
|
|
15
15
|
paddingVertical: 0,
|
|
16
16
|
gap: 0,
|
|
17
17
|
},
|
|
@@ -20,8 +20,8 @@ const createStyles = (spacingMultiplier: number) => StyleSheet.create({
|
|
|
20
20
|
height: calculateResponsiveSize(NAVIGATION.backButton.height, spacingMultiplier),
|
|
21
21
|
borderRadius: 0,
|
|
22
22
|
backgroundColor: '',
|
|
23
|
-
justifyContent: 'center',
|
|
24
|
-
alignItems: 'center',
|
|
23
|
+
justifyContent: 'center' as const,
|
|
24
|
+
alignItems: 'center' as const,
|
|
25
25
|
borderWidth: 1,
|
|
26
26
|
borderColor: '',
|
|
27
27
|
},
|
|
@@ -29,13 +29,13 @@ const createStyles = (spacingMultiplier: number) => StyleSheet.create({
|
|
|
29
29
|
flex: 1,
|
|
30
30
|
height: calculateResponsiveSize(NAVIGATION.backButton.height, spacingMultiplier),
|
|
31
31
|
borderRadius: 0,
|
|
32
|
-
overflow: 'hidden',
|
|
32
|
+
overflow: 'hidden' as const,
|
|
33
33
|
},
|
|
34
34
|
actionContent: {
|
|
35
35
|
flex: 1,
|
|
36
|
-
flexDirection: 'row',
|
|
37
|
-
alignItems: 'center',
|
|
38
|
-
justifyContent: 'flex-start',
|
|
36
|
+
flexDirection: 'row' as const,
|
|
37
|
+
alignItems: 'center' as const,
|
|
38
|
+
justifyContent: 'flex-start' as const,
|
|
39
39
|
backgroundColor: '',
|
|
40
40
|
gap: 0,
|
|
41
41
|
paddingHorizontal: 0,
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* AlertModal Component
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import React, { useMemo } from 'react';
|
|
6
|
-
import {
|
|
5
|
+
import React, { useMemo, useCallback } from 'react';
|
|
6
|
+
import { View, Modal, Pressable } from 'react-native';
|
|
7
7
|
import { AtomicButton, AtomicText, AtomicIcon } from '../../atoms';
|
|
8
8
|
import { useAppDesignTokens } from '../../theme';
|
|
9
9
|
import { Alert, AlertType } from './AlertTypes';
|
|
@@ -35,43 +35,54 @@ export const AlertModal: React.FC<AlertModalProps> = ({ alert }) => {
|
|
|
35
35
|
const iconName = getAlertIconName(alert.type);
|
|
36
36
|
const hasTwoActions = alert.actions.length === 2;
|
|
37
37
|
|
|
38
|
-
const
|
|
38
|
+
const handleActionPress = useCallback(async (action: typeof alert.actions[0]) => {
|
|
39
|
+
await action.onPress();
|
|
40
|
+
if (action.closeOnPress ?? true) {
|
|
41
|
+
handleClose();
|
|
42
|
+
}
|
|
43
|
+
}, [handleClose]);
|
|
44
|
+
|
|
45
|
+
const styles = useMemo(() => ({
|
|
39
46
|
overlay: {
|
|
40
47
|
flex: 1,
|
|
41
|
-
justifyContent: 'center',
|
|
42
|
-
alignItems: 'center',
|
|
48
|
+
justifyContent: 'center' as const,
|
|
49
|
+
alignItems: 'center' as const,
|
|
43
50
|
padding: calculateResponsiveSize(MODAL_SIZES.overlayPadding, spacingMultiplier),
|
|
44
51
|
},
|
|
45
52
|
backdrop: {
|
|
46
|
-
|
|
53
|
+
position: 'absolute' as const,
|
|
54
|
+
top: 0,
|
|
55
|
+
left: 0,
|
|
56
|
+
right: 0,
|
|
57
|
+
bottom: 0,
|
|
47
58
|
backgroundColor: 'rgba(0,0,0,0.55)',
|
|
48
59
|
},
|
|
49
60
|
modal: {
|
|
50
61
|
width: '100%',
|
|
51
62
|
maxWidth: calculateResponsiveSize(MODAL_SIZES.maxWidth, spacingMultiplier),
|
|
52
63
|
padding: calculateResponsiveSize(MODAL_SIZES.padding, spacingMultiplier),
|
|
53
|
-
alignItems: 'center',
|
|
64
|
+
alignItems: 'center' as const,
|
|
54
65
|
},
|
|
55
66
|
iconCircle: {
|
|
56
67
|
width: calculateResponsiveSize(ALERT_MODAL_ICON.width, spacingMultiplier),
|
|
57
68
|
height: calculateResponsiveSize(ALERT_MODAL_ICON.height, spacingMultiplier),
|
|
58
69
|
borderRadius: calculateResponsiveSize(ALERT_MODAL_ICON.borderRadius, spacingMultiplier),
|
|
59
|
-
justifyContent: 'center',
|
|
60
|
-
alignItems: 'center',
|
|
70
|
+
justifyContent: 'center' as const,
|
|
71
|
+
alignItems: 'center' as const,
|
|
61
72
|
marginBottom: calculateResponsiveSize(ALERT_MODAL_ICON.marginBottom, spacingMultiplier),
|
|
62
73
|
},
|
|
63
74
|
title: {
|
|
64
|
-
fontWeight: '700',
|
|
65
|
-
textAlign: 'center',
|
|
75
|
+
fontWeight: '700' as const,
|
|
76
|
+
textAlign: 'center' as const,
|
|
66
77
|
marginBottom: tokens.spacing.sm,
|
|
67
78
|
},
|
|
68
79
|
message: {
|
|
69
|
-
textAlign: 'center',
|
|
80
|
+
textAlign: 'center' as const,
|
|
70
81
|
lineHeight: calculateResponsiveSize(24, spacingMultiplier),
|
|
71
82
|
opacity: 0.85,
|
|
72
83
|
},
|
|
73
84
|
actionsRow: {
|
|
74
|
-
flexDirection: 'row',
|
|
85
|
+
flexDirection: 'row' as const,
|
|
75
86
|
width: '100%',
|
|
76
87
|
},
|
|
77
88
|
actionsColumn: {
|
|
@@ -170,12 +181,7 @@ export const AlertModal: React.FC<AlertModalProps> = ({ alert }) => {
|
|
|
170
181
|
: action.style === 'secondary' ? 'outline'
|
|
171
182
|
: 'primary'
|
|
172
183
|
}
|
|
173
|
-
onPress={
|
|
174
|
-
await action.onPress();
|
|
175
|
-
if (action.closeOnPress ?? true) {
|
|
176
|
-
handleClose();
|
|
177
|
-
}
|
|
178
|
-
}}
|
|
184
|
+
onPress={() => handleActionPress(action)}
|
|
179
185
|
fullWidth={!hasTwoActions}
|
|
180
186
|
style={hasTwoActions ? styles.actionButtonHalf : undefined}
|
|
181
187
|
/>
|
|
@@ -56,6 +56,10 @@ export const BottomSheet = forwardRef<BottomSheetRef, BottomSheetProps>((props,
|
|
|
56
56
|
onChange?.(-1);
|
|
57
57
|
}, [onClose, onChange]);
|
|
58
58
|
|
|
59
|
+
const handleContentPress = useCallback((e: any) => {
|
|
60
|
+
e.stopPropagation();
|
|
61
|
+
}, []);
|
|
62
|
+
|
|
59
63
|
useImperativeHandle(ref, () => ({
|
|
60
64
|
snapToIndex: (index: number) => {
|
|
61
65
|
if (index >= 0) present();
|
|
@@ -69,11 +73,11 @@ export const BottomSheet = forwardRef<BottomSheetRef, BottomSheetProps>((props,
|
|
|
69
73
|
|
|
70
74
|
const spacingMultiplier = tokens.spacingMultiplier;
|
|
71
75
|
|
|
72
|
-
const styles = useMemo(() =>
|
|
76
|
+
const styles = useMemo(() => ({
|
|
73
77
|
overlay: {
|
|
74
78
|
flex: 1,
|
|
75
79
|
backgroundColor: tokens.colors.modalOverlay,
|
|
76
|
-
justifyContent: 'flex-end',
|
|
80
|
+
justifyContent: 'flex-end' as const,
|
|
77
81
|
},
|
|
78
82
|
container: {
|
|
79
83
|
height: sheetHeight,
|
|
@@ -87,7 +91,7 @@ export const BottomSheet = forwardRef<BottomSheetRef, BottomSheetProps>((props,
|
|
|
87
91
|
height: calculateResponsiveSize(BOTTOM_SHEET_HANDLE.height, spacingMultiplier),
|
|
88
92
|
backgroundColor: tokens.colors.border,
|
|
89
93
|
borderRadius: calculateResponsiveSize(BOTTOM_SHEET_HANDLE.borderRadius, spacingMultiplier),
|
|
90
|
-
alignSelf: 'center',
|
|
94
|
+
alignSelf: 'center' as const,
|
|
91
95
|
marginTop: tokens.spacing.md,
|
|
92
96
|
marginBottom: tokens.spacing.sm,
|
|
93
97
|
},
|
|
@@ -107,7 +111,7 @@ export const BottomSheet = forwardRef<BottomSheetRef, BottomSheetProps>((props,
|
|
|
107
111
|
>
|
|
108
112
|
<Pressable style={styles.overlay} onPress={dismiss} accessibilityLabel="Close" accessibilityRole="button">
|
|
109
113
|
<View style={styles.container}>
|
|
110
|
-
<Pressable onPress={
|
|
114
|
+
<Pressable onPress={handleContentPress} style={styles.content} accessibilityRole="none">
|
|
111
115
|
<View style={styles.handle} />
|
|
112
116
|
{children}
|
|
113
117
|
</Pressable>
|
|
@@ -55,11 +55,11 @@ export const BottomSheetModal = forwardRef<BottomSheetModalRef, BottomSheetModal
|
|
|
55
55
|
|
|
56
56
|
const spacingMultiplier = tokens.spacingMultiplier;
|
|
57
57
|
|
|
58
|
-
const styles = useMemo(() =>
|
|
58
|
+
const styles = useMemo(() => ({
|
|
59
59
|
overlay: {
|
|
60
60
|
flex: 1,
|
|
61
61
|
backgroundColor: tokens.colors.modalOverlay,
|
|
62
|
-
justifyContent: 'flex-end',
|
|
62
|
+
justifyContent: 'flex-end' as const,
|
|
63
63
|
},
|
|
64
64
|
container: {
|
|
65
65
|
height: sheetHeight,
|
|
@@ -67,19 +67,20 @@ export const BottomSheetModal = forwardRef<BottomSheetModalRef, BottomSheetModal
|
|
|
67
67
|
borderTopLeftRadius: borderRadius,
|
|
68
68
|
borderTopRightRadius: borderRadius,
|
|
69
69
|
paddingBottom: Math.max(insets.bottom, tokens.spacing.xs),
|
|
70
|
+
width: '100%' as const,
|
|
71
|
+
},
|
|
72
|
+
contentWrapper: {
|
|
73
|
+
flex: 1,
|
|
70
74
|
},
|
|
71
75
|
handle: {
|
|
72
76
|
width: calculateResponsiveSize(BOTTOM_SHEET_HANDLE.width, spacingMultiplier),
|
|
73
77
|
height: calculateResponsiveSize(BOTTOM_SHEET_HANDLE.height, spacingMultiplier),
|
|
74
78
|
backgroundColor: tokens.colors.border,
|
|
75
79
|
borderRadius: calculateResponsiveSize(BOTTOM_SHEET_HANDLE.borderRadius, spacingMultiplier),
|
|
76
|
-
alignSelf: 'center',
|
|
80
|
+
alignSelf: 'center' as const,
|
|
77
81
|
marginTop: tokens.spacing.md,
|
|
78
82
|
marginBottom: tokens.spacing.sm,
|
|
79
83
|
},
|
|
80
|
-
content: {
|
|
81
|
-
flex: 1,
|
|
82
|
-
},
|
|
83
84
|
}), [sheetHeight, backgroundColor, tokens, borderRadius, insets.bottom, spacingMultiplier]);
|
|
84
85
|
|
|
85
86
|
return (
|
|
@@ -91,13 +92,13 @@ export const BottomSheetModal = forwardRef<BottomSheetModalRef, BottomSheetModal
|
|
|
91
92
|
statusBarTranslucent
|
|
92
93
|
>
|
|
93
94
|
<View style={styles.overlay}>
|
|
94
|
-
<Pressable
|
|
95
|
-
style={StyleSheet.absoluteFill}
|
|
95
|
+
<Pressable
|
|
96
|
+
style={StyleSheet.absoluteFill}
|
|
96
97
|
onPress={dismiss}
|
|
97
98
|
accessibilityLabel="Close modal"
|
|
98
99
|
/>
|
|
99
|
-
<View style={
|
|
100
|
-
<View style={
|
|
100
|
+
<View style={styles.container}>
|
|
101
|
+
<View style={styles.contentWrapper}>
|
|
101
102
|
<View style={styles.handle} />
|
|
102
103
|
{children}
|
|
103
104
|
</View>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { useMemo } from "react";
|
|
2
|
-
import { View,
|
|
2
|
+
import { View, TouchableOpacity } from "react-native";
|
|
3
3
|
import { AtomicIcon } from "../../atoms";
|
|
4
4
|
import { AtomicText } from "../../atoms";
|
|
5
5
|
import { useAppDesignTokens } from "../../theme";
|
|
@@ -20,22 +20,29 @@ export const CircularMenuItem: React.FC<CircularMenuItemProps> = React.memo(({
|
|
|
20
20
|
const tokens = useAppDesignTokens();
|
|
21
21
|
const spacingMultiplier = tokens.spacingMultiplier;
|
|
22
22
|
|
|
23
|
-
const styles = useMemo(() =>
|
|
23
|
+
const styles = useMemo(() => ({
|
|
24
24
|
container: {
|
|
25
|
-
alignItems: "center",
|
|
25
|
+
alignItems: "center" as const,
|
|
26
26
|
gap: 6,
|
|
27
27
|
width: LAYOUT.ITEM_SIZE,
|
|
28
28
|
},
|
|
29
29
|
iconContainer: {
|
|
30
|
-
justifyContent: "center",
|
|
31
|
-
alignItems: "center",
|
|
30
|
+
justifyContent: "center" as const,
|
|
31
|
+
alignItems: "center" as const,
|
|
32
|
+
backgroundColor: tokens.colors.surfaceVariant,
|
|
33
|
+
width: LAYOUT.ICON_SIZE,
|
|
34
|
+
height: LAYOUT.ICON_SIZE,
|
|
35
|
+
borderRadius: LAYOUT.ICON_SIZE / 2,
|
|
36
|
+
borderWidth: 1,
|
|
37
|
+
borderColor: tokens.colors.border,
|
|
32
38
|
},
|
|
33
39
|
label: {
|
|
34
40
|
fontSize: calculateResponsiveSize(11, spacingMultiplier),
|
|
35
|
-
fontWeight: "500",
|
|
36
|
-
textAlign: "center",
|
|
41
|
+
fontWeight: "500" as const,
|
|
42
|
+
textAlign: "center" as const,
|
|
43
|
+
color: tokens.colors.textPrimary,
|
|
37
44
|
},
|
|
38
|
-
}), [spacingMultiplier]);
|
|
45
|
+
}), [spacingMultiplier, tokens.colors.surfaceVariant, tokens.colors.border, tokens.colors.textPrimary]);
|
|
39
46
|
|
|
40
47
|
return (
|
|
41
48
|
<TouchableOpacity
|
|
@@ -45,24 +52,12 @@ export const CircularMenuItem: React.FC<CircularMenuItemProps> = React.memo(({
|
|
|
45
52
|
accessibilityRole="button"
|
|
46
53
|
accessibilityLabel={label}
|
|
47
54
|
>
|
|
48
|
-
<View
|
|
49
|
-
style={[
|
|
50
|
-
styles.iconContainer,
|
|
51
|
-
{
|
|
52
|
-
backgroundColor: tokens.colors.surfaceVariant,
|
|
53
|
-
width: LAYOUT.ICON_SIZE,
|
|
54
|
-
height: LAYOUT.ICON_SIZE,
|
|
55
|
-
borderRadius: LAYOUT.ICON_SIZE / 2,
|
|
56
|
-
borderWidth: 1,
|
|
57
|
-
borderColor: tokens.colors.border,
|
|
58
|
-
},
|
|
59
|
-
]}
|
|
60
|
-
>
|
|
55
|
+
<View style={styles.iconContainer}>
|
|
61
56
|
<AtomicIcon name={icon} size="lg" color="primary" />
|
|
62
57
|
</View>
|
|
63
58
|
<AtomicText
|
|
64
59
|
type="labelSmall"
|
|
65
|
-
style={
|
|
60
|
+
style={styles.label}
|
|
66
61
|
>
|
|
67
62
|
{label}
|
|
68
63
|
</AtomicText>
|
|
@@ -30,9 +30,29 @@ export const CountdownHeader: React.FC<CountdownHeaderProps> = ({
|
|
|
30
30
|
[spacingMultiplier]
|
|
31
31
|
);
|
|
32
32
|
|
|
33
|
+
const containerStyle = useMemo(() => [
|
|
34
|
+
styles.container,
|
|
35
|
+
{ marginBottom: tokens.spacing.md },
|
|
36
|
+
], [tokens.spacing.md]);
|
|
37
|
+
|
|
38
|
+
const titleRowStyle = useMemo(() => [
|
|
39
|
+
styles.titleRow,
|
|
40
|
+
{ gap: tokens.spacing.sm },
|
|
41
|
+
], [tokens.spacing.sm]);
|
|
42
|
+
|
|
43
|
+
const toggleButtonStyle = useMemo(() => [
|
|
44
|
+
styles.toggleButton,
|
|
45
|
+
{
|
|
46
|
+
backgroundColor: tokens.colors.surfaceSecondary,
|
|
47
|
+
width: toggleButtonSize,
|
|
48
|
+
height: toggleButtonSize,
|
|
49
|
+
borderRadius: toggleButtonSize / 2,
|
|
50
|
+
},
|
|
51
|
+
], [styles.toggleButton, tokens.colors.surfaceSecondary, toggleButtonSize]);
|
|
52
|
+
|
|
33
53
|
return (
|
|
34
|
-
<View style={
|
|
35
|
-
<View style={
|
|
54
|
+
<View style={containerStyle}>
|
|
55
|
+
<View style={titleRowStyle}>
|
|
36
56
|
{icon && (
|
|
37
57
|
<AtomicIcon
|
|
38
58
|
name={icon}
|
|
@@ -51,15 +71,7 @@ export const CountdownHeader: React.FC<CountdownHeaderProps> = ({
|
|
|
51
71
|
|
|
52
72
|
{showToggle && onToggle && (
|
|
53
73
|
<TouchableOpacity
|
|
54
|
-
style={
|
|
55
|
-
styles.toggleButton,
|
|
56
|
-
{
|
|
57
|
-
backgroundColor: tokens.colors.surfaceSecondary,
|
|
58
|
-
width: toggleButtonSize,
|
|
59
|
-
height: toggleButtonSize,
|
|
60
|
-
borderRadius: toggleButtonSize / 2,
|
|
61
|
-
},
|
|
62
|
-
]}
|
|
74
|
+
style={toggleButtonStyle}
|
|
63
75
|
onPress={onToggle}
|
|
64
76
|
accessibilityRole="button"
|
|
65
77
|
accessibilityLabel="Toggle view"
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { useMemo } from 'react';
|
|
2
|
-
import { View
|
|
2
|
+
import { View } from 'react-native';
|
|
3
3
|
import { AtomicText } from '../../../atoms';
|
|
4
4
|
import { useAppDesignTokens } from '../../../theme';
|
|
5
5
|
import { calculateResponsiveSize } from '../../../responsive';
|
|
@@ -19,25 +19,25 @@ export const TimeUnit: React.FC<TimeUnitProps> = ({
|
|
|
19
19
|
const tokens = useAppDesignTokens();
|
|
20
20
|
const spacingMultiplier = tokens.spacingMultiplier;
|
|
21
21
|
|
|
22
|
-
const styles = useMemo(() =>
|
|
22
|
+
const styles = useMemo(() => ({
|
|
23
23
|
container: {
|
|
24
24
|
flex: 1,
|
|
25
|
-
alignItems: 'center',
|
|
26
|
-
justifyContent: 'center',
|
|
25
|
+
alignItems: 'center' as const,
|
|
26
|
+
justifyContent: 'center' as const,
|
|
27
27
|
},
|
|
28
28
|
value: {
|
|
29
|
-
fontWeight: '700',
|
|
29
|
+
fontWeight: '700' as const,
|
|
30
30
|
lineHeight: calculateResponsiveSize(38, spacingMultiplier),
|
|
31
31
|
},
|
|
32
32
|
label: {
|
|
33
|
-
fontWeight: '600',
|
|
33
|
+
fontWeight: '600' as const,
|
|
34
34
|
marginTop: calculateResponsiveSize(2, spacingMultiplier),
|
|
35
35
|
letterSpacing: 1,
|
|
36
|
-
textTransform: 'uppercase',
|
|
36
|
+
textTransform: 'uppercase' as const,
|
|
37
37
|
},
|
|
38
38
|
}), [spacingMultiplier]);
|
|
39
39
|
|
|
40
|
-
const sizeConfig = {
|
|
40
|
+
const sizeConfig = useMemo(() => ({
|
|
41
41
|
small: {
|
|
42
42
|
fontSize: calculateResponsiveSize(COUNTDOWN_SIZES.small.fontSize, spacingMultiplier),
|
|
43
43
|
padding: tokens.spacing.sm,
|
|
@@ -53,7 +53,7 @@ export const TimeUnit: React.FC<TimeUnitProps> = ({
|
|
|
53
53
|
padding: tokens.spacing.lg,
|
|
54
54
|
minHeight: calculateResponsiveSize(COUNTDOWN_SIZES.large.minHeight, spacingMultiplier),
|
|
55
55
|
},
|
|
56
|
-
};
|
|
56
|
+
}), [spacingMultiplier, tokens.spacing.sm, tokens.spacing.md, tokens.spacing.lg]);
|
|
57
57
|
|
|
58
58
|
const config = sizeConfig[size];
|
|
59
59
|
|
|
@@ -70,23 +70,28 @@ export const TimeUnit: React.FC<TimeUnitProps> = ({
|
|
|
70
70
|
|
|
71
71
|
const calculatedFontSize = config.fontSize * fontSizeMultiplier;
|
|
72
72
|
|
|
73
|
+
const containerStyle = useMemo(() => [
|
|
74
|
+
styles.container,
|
|
75
|
+
{
|
|
76
|
+
backgroundColor: tokens.colors.surfaceSecondary,
|
|
77
|
+
borderRadius: tokens.borders.radius.lg,
|
|
78
|
+
paddingVertical: config.padding,
|
|
79
|
+
minHeight: config.minHeight,
|
|
80
|
+
paddingHorizontal: tokens.spacing.xs,
|
|
81
|
+
},
|
|
82
|
+
], [styles.container, tokens.colors.surfaceSecondary, tokens.borders.radius.lg, config.padding, config.minHeight, tokens.spacing.xs]);
|
|
83
|
+
|
|
84
|
+
const valueStyle = useMemo(() => [
|
|
85
|
+
styles.value,
|
|
86
|
+
{ fontSize: calculatedFontSize },
|
|
87
|
+
], [styles.value, calculatedFontSize]);
|
|
88
|
+
|
|
73
89
|
return (
|
|
74
|
-
<View
|
|
75
|
-
style={[
|
|
76
|
-
styles.container,
|
|
77
|
-
{
|
|
78
|
-
backgroundColor: tokens.colors.surfaceSecondary,
|
|
79
|
-
borderRadius: tokens.borders.radius.lg,
|
|
80
|
-
paddingVertical: config.padding,
|
|
81
|
-
minHeight: config.minHeight,
|
|
82
|
-
paddingHorizontal: tokens.spacing.xs,
|
|
83
|
-
},
|
|
84
|
-
]}
|
|
85
|
-
>
|
|
90
|
+
<View style={containerStyle}>
|
|
86
91
|
<AtomicText
|
|
87
92
|
type="displaySmall"
|
|
88
93
|
color="onSurface"
|
|
89
|
-
style={
|
|
94
|
+
style={valueStyle}
|
|
90
95
|
numberOfLines={1}
|
|
91
96
|
>
|
|
92
97
|
{displayValue}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
|
|
2
2
|
import React, { useMemo } from 'react';
|
|
3
|
-
import { View,
|
|
3
|
+
import { View, Text, Image } from 'react-native';
|
|
4
4
|
import { useAppDesignTokens } from '../../theme';
|
|
5
5
|
import type { HeroSectionProps } from './types';
|
|
6
6
|
import { calculateResponsiveSize } from '../../responsive';
|
|
@@ -28,7 +28,11 @@ export const HeroSection: React.FC<HeroSectionProps> = ({
|
|
|
28
28
|
overflow: 'hidden' as const,
|
|
29
29
|
},
|
|
30
30
|
background: {
|
|
31
|
-
|
|
31
|
+
position: 'absolute' as const,
|
|
32
|
+
top: 0,
|
|
33
|
+
left: 0,
|
|
34
|
+
right: 0,
|
|
35
|
+
bottom: 0,
|
|
32
36
|
backgroundColor: tokens.colors.surfaceVariant,
|
|
33
37
|
},
|
|
34
38
|
iconWrapper: {
|
|
@@ -46,19 +50,23 @@ export const HeroSection: React.FC<HeroSectionProps> = ({
|
|
|
46
50
|
[tokens, height, spacingMultiplier],
|
|
47
51
|
);
|
|
48
52
|
|
|
49
|
-
const styles = useMemo(() =>
|
|
53
|
+
const styles = useMemo(() => ({
|
|
50
54
|
image: {
|
|
51
|
-
|
|
55
|
+
position: 'absolute' as const,
|
|
56
|
+
top: 0,
|
|
57
|
+
left: 0,
|
|
58
|
+
right: 0,
|
|
59
|
+
bottom: 0,
|
|
52
60
|
width: '100%',
|
|
53
61
|
height: '100%',
|
|
54
62
|
},
|
|
55
63
|
emoji: {
|
|
56
64
|
fontSize: calculateResponsiveSize(64, spacingMultiplier),
|
|
57
|
-
textAlign: 'left',
|
|
65
|
+
textAlign: 'left' as const,
|
|
58
66
|
includeFontPadding: false,
|
|
59
67
|
},
|
|
60
68
|
fadeOverlay: {
|
|
61
|
-
position: 'absolute',
|
|
69
|
+
position: 'absolute' as const,
|
|
62
70
|
bottom: -1,
|
|
63
71
|
left: 0,
|
|
64
72
|
right: 0,
|
|
@@ -10,7 +10,6 @@ import React, { useCallback, useMemo, useState } from 'react';
|
|
|
10
10
|
import {
|
|
11
11
|
View,
|
|
12
12
|
TouchableOpacity,
|
|
13
|
-
StyleSheet,
|
|
14
13
|
type LayoutChangeEvent,
|
|
15
14
|
type StyleProp,
|
|
16
15
|
type ViewStyle,
|
|
@@ -152,7 +151,7 @@ export const IconGrid = React.memo<IconGridProps>(({
|
|
|
152
151
|
// Stable renderItem with memoization
|
|
153
152
|
const renderItem = useCallback(({ item }: { item: IconGridItem }) => {
|
|
154
153
|
if (itemWidth === 0) {
|
|
155
|
-
return <View key={item.id} style={
|
|
154
|
+
return <View key={item.id} style={styles.placeholder} />;
|
|
156
155
|
}
|
|
157
156
|
|
|
158
157
|
return (
|
|
@@ -177,7 +176,7 @@ export const IconGrid = React.memo<IconGridProps>(({
|
|
|
177
176
|
return (
|
|
178
177
|
<View style={gridStyle} onLayout={handleLayout}>
|
|
179
178
|
{items.map((item) => (
|
|
180
|
-
<View key={item.id} style={
|
|
179
|
+
<View key={item.id} style={styles.placeholder} />
|
|
181
180
|
))}
|
|
182
181
|
</View>
|
|
183
182
|
);
|
|
@@ -198,25 +197,29 @@ export const IconGrid = React.memo<IconGridProps>(({
|
|
|
198
197
|
);
|
|
199
198
|
});
|
|
200
199
|
|
|
201
|
-
const createStyles = (spacingMultiplier: number) =>
|
|
200
|
+
const createStyles = (spacingMultiplier: number) => ({
|
|
202
201
|
grid: {
|
|
203
|
-
flexDirection: 'row',
|
|
204
|
-
flexWrap: 'wrap',
|
|
202
|
+
flexDirection: 'row' as const,
|
|
203
|
+
flexWrap: 'wrap' as const,
|
|
205
204
|
},
|
|
206
205
|
card: {
|
|
207
|
-
alignItems: 'center',
|
|
206
|
+
alignItems: 'center' as const,
|
|
208
207
|
gap: 8,
|
|
209
208
|
},
|
|
210
209
|
iconBox: {
|
|
211
210
|
aspectRatio: 1,
|
|
212
211
|
borderRadius: calculateResponsiveSize(ICON_GRID.borderRadius, spacingMultiplier),
|
|
213
|
-
alignItems: 'center',
|
|
214
|
-
justifyContent: 'center',
|
|
212
|
+
alignItems: 'center' as const,
|
|
213
|
+
justifyContent: 'center' as const,
|
|
215
214
|
borderWidth: 1,
|
|
216
215
|
},
|
|
217
216
|
label: {
|
|
218
217
|
fontSize: calculateResponsiveSize(ICON_GRID.fontSize, spacingMultiplier),
|
|
219
|
-
fontWeight: '700',
|
|
220
|
-
textAlign: 'center',
|
|
218
|
+
fontWeight: '700' as const,
|
|
219
|
+
textAlign: 'center' as const,
|
|
220
|
+
},
|
|
221
|
+
placeholder: {
|
|
222
|
+
width: 0,
|
|
223
|
+
height: 0,
|
|
221
224
|
},
|
|
222
225
|
});
|