@umituz/react-native-design-system 4.28.13 → 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/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 +5 -1
- 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",
|
|
@@ -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();
|
|
@@ -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
|
});
|