@umituz/react-native-design-system 4.28.4 → 4.28.6
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/AtomicInput.tsx +2 -2
- package/src/index.ts +1 -1
- package/src/molecules/Divider/Divider.tsx +2 -3
- package/src/molecules/Divider/types.ts +22 -5
- package/src/molecules/StepHeader/StepHeader.constants.ts +48 -0
- package/src/molecules/StepHeader/StepHeader.tsx +29 -23
- package/src/molecules/StepProgress/StepProgress.constants.ts +23 -0
- package/src/molecules/StepProgress/StepProgress.tsx +9 -6
- package/src/molecules/avatar/Avatar.constants.ts +20 -2
- package/src/molecules/avatar/Avatar.tsx +5 -3
- package/src/molecules/avatar/Avatar.utils.ts +4 -4
- package/src/molecules/avatar/AvatarGroup.tsx +2 -2
- package/src/molecules/listitem/styles/listItemStyles.ts +2 -3
- package/src/molecules/navigation/hooks/useAppFocusEffect.ts +14 -11
- package/src/molecules/navigation/hooks/useAppIsFocused.ts +1 -2
- package/src/molecules/navigation/hooks/useAppNavigation.ts +88 -118
- package/src/molecules/navigation/hooks/useAppRoute.ts +26 -27
- package/src/onboarding/domain/entities/ChatMessage.ts +19 -0
- package/src/onboarding/domain/entities/ChatStep.ts +72 -0
- package/src/onboarding/index.ts +29 -0
- package/src/onboarding/infrastructure/hooks/useChatAnimations.ts +145 -0
- package/src/onboarding/presentation/components/chat/ChatMessage.tsx +166 -0
- package/src/onboarding/presentation/components/chat/ChatOptionButton.tsx +145 -0
- package/src/onboarding/presentation/components/chat/TypingIndicator.tsx +99 -0
- package/src/onboarding/presentation/components/chat/index.ts +12 -0
- package/src/onboarding/presentation/hooks/useChatOnboarding.ts +278 -0
- package/src/onboarding/presentation/screens/ChatOnboardingScreen.tsx +276 -0
- package/src/utils/index.ts +13 -0
- package/src/utils/responsiveUtils.ts +110 -0
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.6",
|
|
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",
|
|
@@ -92,12 +92,12 @@ export const AtomicInput = React.forwardRef<React.ElementRef<typeof TextInput>,
|
|
|
92
92
|
fontSize: sizeConfig.fontSize,
|
|
93
93
|
lineHeight: (sizeConfig.fontSize || 16) * 1.2,
|
|
94
94
|
color: textColor,
|
|
95
|
-
paddingVertical: 4,
|
|
95
|
+
paddingVertical: Math.floor(4 * tokens.spacingMultiplier),
|
|
96
96
|
paddingLeft: leadingIcon ? iconPadding : undefined,
|
|
97
97
|
paddingRight: (trailingIcon || showPasswordToggle) ? iconPadding : undefined,
|
|
98
98
|
},
|
|
99
99
|
inputStyle,
|
|
100
|
-
], [sizeConfig, textColor, leadingIcon, trailingIcon, showPasswordToggle, iconPadding, inputStyle]);
|
|
100
|
+
], [sizeConfig, textColor, leadingIcon, trailingIcon, showPasswordToggle, iconPadding, inputStyle, tokens.spacingMultiplier]);
|
|
101
101
|
|
|
102
102
|
return (
|
|
103
103
|
<View testID={testID}>
|
package/src/index.ts
CHANGED
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
* /tanstack - TanstackProvider
|
|
32
32
|
* /loading - LoadingProvider, useGlobalLoading
|
|
33
33
|
* /haptics - HapticService, useHaptics
|
|
34
|
-
* /onboarding - OnboardingScreen
|
|
34
|
+
* /onboarding - OnboardingScreen, ChatOnboardingScreen (chat-based)
|
|
35
35
|
* /gallery - gallerySaveService
|
|
36
36
|
* /carousel - Carousel components
|
|
37
37
|
* /init - createAppInitializer, createEnvConfig
|
|
@@ -75,7 +75,7 @@ export const Divider: React.FC<DividerProps> = ({
|
|
|
75
75
|
style,
|
|
76
76
|
}) => {
|
|
77
77
|
const tokens = useAppDesignTokens();
|
|
78
|
-
const spacingValue = DividerUtils.getSpacing(spacing);
|
|
78
|
+
const spacingValue = DividerUtils.getSpacing(spacing, tokens.spacingMultiplier);
|
|
79
79
|
const borderColor = color || tokens.colors.border;
|
|
80
80
|
|
|
81
81
|
// Determine border style based on lineStyle
|
|
@@ -144,7 +144,7 @@ export const Divider: React.FC<DividerProps> = ({
|
|
|
144
144
|
<AtomicText
|
|
145
145
|
type="bodySmall"
|
|
146
146
|
color="secondary"
|
|
147
|
-
style={styles.textLabel}
|
|
147
|
+
style={[styles.textLabel, { marginHorizontal: Math.floor(12 * tokens.spacingMultiplier) }]}
|
|
148
148
|
>
|
|
149
149
|
{text}
|
|
150
150
|
</AtomicText>
|
|
@@ -181,7 +181,6 @@ const styles = StyleSheet.create({
|
|
|
181
181
|
flex: 1,
|
|
182
182
|
},
|
|
183
183
|
textLabel: {
|
|
184
|
-
marginHorizontal: 12,
|
|
185
184
|
fontWeight: '500',
|
|
186
185
|
},
|
|
187
186
|
});
|
|
@@ -42,24 +42,41 @@ export interface DividerConfig {
|
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
/**
|
|
45
|
-
*
|
|
45
|
+
* Base spacing configurations (px) - will be multiplied by spacingMultiplier
|
|
46
46
|
*/
|
|
47
|
-
|
|
47
|
+
const BASE_SPACING_CONFIGS: Record<DividerSpacing, number> = {
|
|
48
48
|
none: 0,
|
|
49
49
|
small: 8,
|
|
50
50
|
medium: 16,
|
|
51
51
|
large: 24,
|
|
52
52
|
};
|
|
53
53
|
|
|
54
|
+
/**
|
|
55
|
+
* Get responsive spacing value
|
|
56
|
+
* Multiplies base spacing by spacingMultiplier for tablet/small device support
|
|
57
|
+
*/
|
|
58
|
+
export const getSpacingConfigs = (spacingMultiplier: number): Record<DividerSpacing, number> => {
|
|
59
|
+
return Object.entries(BASE_SPACING_CONFIGS).reduce((acc, [key, value]) => {
|
|
60
|
+
acc[key as DividerSpacing] = Math.floor(value * spacingMultiplier);
|
|
61
|
+
return acc;
|
|
62
|
+
}, {} as Record<DividerSpacing, number>);
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* @deprecated Use getSpacingConfigs(spacingMultiplier) instead
|
|
67
|
+
* Kept for backward compatibility
|
|
68
|
+
*/
|
|
69
|
+
export const SPACING_CONFIGS = BASE_SPACING_CONFIGS;
|
|
70
|
+
|
|
54
71
|
/**
|
|
55
72
|
* Divider utility class
|
|
56
73
|
*/
|
|
57
74
|
export class DividerUtils {
|
|
58
75
|
/**
|
|
59
|
-
* Get spacing value
|
|
76
|
+
* Get spacing value (responsive)
|
|
60
77
|
*/
|
|
61
|
-
static getSpacing(spacing: DividerSpacing): number {
|
|
62
|
-
return
|
|
78
|
+
static getSpacing(spacing: DividerSpacing, spacingMultiplier: number = 1): number {
|
|
79
|
+
return Math.floor(BASE_SPACING_CONFIGS[spacing] * spacingMultiplier);
|
|
63
80
|
}
|
|
64
81
|
|
|
65
82
|
/**
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* StepHeader Constants
|
|
3
|
+
*
|
|
4
|
+
* Base size configurations for StepHeader component.
|
|
5
|
+
* All sizes will be multiplied by spacingMultiplier at runtime.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Base font sizes (px) - before spacingMultiplier
|
|
10
|
+
*/
|
|
11
|
+
export const BASE_FONT_SIZES = {
|
|
12
|
+
title: 28,
|
|
13
|
+
subtitle: 16,
|
|
14
|
+
} as const;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Base spacing (px) - before spacingMultiplier
|
|
18
|
+
*/
|
|
19
|
+
export const BASE_SPACING = {
|
|
20
|
+
marginBottom: 32,
|
|
21
|
+
paddingHorizontal: 24,
|
|
22
|
+
stepIndicatorMarginBottom: 12,
|
|
23
|
+
titleMarginBottom: 12,
|
|
24
|
+
} as const;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Base step dot sizes (px) - before spacingMultiplier
|
|
28
|
+
*/
|
|
29
|
+
export const BASE_STEP_DOT_SIZES = {
|
|
30
|
+
width: 8,
|
|
31
|
+
height: 8,
|
|
32
|
+
borderRadius: 4,
|
|
33
|
+
marginHorizontal: 4,
|
|
34
|
+
} as const;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Default configuration
|
|
38
|
+
*/
|
|
39
|
+
export const DEFAULT_CONFIG = {
|
|
40
|
+
showStepIndicator: false,
|
|
41
|
+
titleAlignment: "left" as const,
|
|
42
|
+
titleFontSize: BASE_FONT_SIZES.title,
|
|
43
|
+
subtitleFontSize: BASE_FONT_SIZES.subtitle,
|
|
44
|
+
spacing: {
|
|
45
|
+
marginBottom: BASE_SPACING.marginBottom,
|
|
46
|
+
paddingHorizontal: BASE_SPACING.paddingHorizontal,
|
|
47
|
+
},
|
|
48
|
+
} as const;
|
|
@@ -9,6 +9,16 @@ import React, { useMemo } from "react";
|
|
|
9
9
|
import { View, StyleSheet, type ViewStyle, type StyleProp } from "react-native";
|
|
10
10
|
import { AtomicText } from "../../atoms/AtomicText";
|
|
11
11
|
import { useAppDesignTokens } from "../../theme/hooks/useAppDesignTokens";
|
|
12
|
+
import {
|
|
13
|
+
calculateResponsiveSize,
|
|
14
|
+
calculateLineHeight,
|
|
15
|
+
} from "../../utils/responsiveUtils";
|
|
16
|
+
import {
|
|
17
|
+
BASE_FONT_SIZES,
|
|
18
|
+
BASE_SPACING,
|
|
19
|
+
BASE_STEP_DOT_SIZES,
|
|
20
|
+
DEFAULT_CONFIG,
|
|
21
|
+
} from "./StepHeader.constants";
|
|
12
22
|
|
|
13
23
|
export interface StepHeaderConfig {
|
|
14
24
|
showStepIndicator?: boolean;
|
|
@@ -30,17 +40,6 @@ export interface StepHeaderProps {
|
|
|
30
40
|
style?: StyleProp<ViewStyle>;
|
|
31
41
|
}
|
|
32
42
|
|
|
33
|
-
const DEFAULT_CONFIG: StepHeaderConfig = {
|
|
34
|
-
showStepIndicator: false,
|
|
35
|
-
titleAlignment: "left",
|
|
36
|
-
titleFontSize: 28,
|
|
37
|
-
subtitleFontSize: 16,
|
|
38
|
-
spacing: {
|
|
39
|
-
marginBottom: 32,
|
|
40
|
-
paddingHorizontal: 24,
|
|
41
|
-
},
|
|
42
|
-
};
|
|
43
|
-
|
|
44
43
|
export const StepHeader: React.FC<StepHeaderProps> = ({
|
|
45
44
|
title,
|
|
46
45
|
subtitle,
|
|
@@ -49,24 +48,31 @@ export const StepHeader: React.FC<StepHeaderProps> = ({
|
|
|
49
48
|
}) => {
|
|
50
49
|
const tokens = useAppDesignTokens();
|
|
51
50
|
const cfg = useMemo(() => ({ ...DEFAULT_CONFIG, ...config }), [config]);
|
|
51
|
+
const spacingMultiplier = tokens.spacingMultiplier;
|
|
52
52
|
|
|
53
53
|
const styles = useMemo(
|
|
54
54
|
() =>
|
|
55
55
|
StyleSheet.create({
|
|
56
56
|
container: {
|
|
57
|
-
paddingHorizontal:
|
|
58
|
-
|
|
57
|
+
paddingHorizontal: calculateResponsiveSize(
|
|
58
|
+
cfg.spacing?.paddingHorizontal ?? BASE_SPACING.paddingHorizontal,
|
|
59
|
+
spacingMultiplier
|
|
60
|
+
),
|
|
61
|
+
marginBottom: calculateResponsiveSize(
|
|
62
|
+
cfg.spacing?.marginBottom ?? BASE_SPACING.marginBottom,
|
|
63
|
+
spacingMultiplier
|
|
64
|
+
),
|
|
59
65
|
},
|
|
60
66
|
stepIndicator: {
|
|
61
67
|
flexDirection: "row",
|
|
62
68
|
alignItems: "center",
|
|
63
|
-
marginBottom:
|
|
69
|
+
marginBottom: calculateResponsiveSize(BASE_SPACING.stepIndicatorMarginBottom, spacingMultiplier),
|
|
64
70
|
},
|
|
65
71
|
stepDot: {
|
|
66
|
-
width:
|
|
67
|
-
height:
|
|
68
|
-
borderRadius:
|
|
69
|
-
marginHorizontal:
|
|
72
|
+
width: calculateResponsiveSize(BASE_STEP_DOT_SIZES.width, spacingMultiplier),
|
|
73
|
+
height: calculateResponsiveSize(BASE_STEP_DOT_SIZES.height, spacingMultiplier),
|
|
74
|
+
borderRadius: calculateResponsiveSize(BASE_STEP_DOT_SIZES.borderRadius, spacingMultiplier),
|
|
75
|
+
marginHorizontal: calculateResponsiveSize(BASE_STEP_DOT_SIZES.marginHorizontal, spacingMultiplier),
|
|
70
76
|
},
|
|
71
77
|
activeDot: {
|
|
72
78
|
backgroundColor: tokens.colors.primary,
|
|
@@ -75,23 +81,23 @@ export const StepHeader: React.FC<StepHeaderProps> = ({
|
|
|
75
81
|
backgroundColor: `${tokens.colors.primary}30`,
|
|
76
82
|
},
|
|
77
83
|
title: {
|
|
78
|
-
fontSize: cfg.titleFontSize,
|
|
84
|
+
fontSize: calculateResponsiveSize(cfg.titleFontSize ?? BASE_FONT_SIZES.title, spacingMultiplier),
|
|
79
85
|
fontWeight: "900",
|
|
80
86
|
color: tokens.colors.textPrimary,
|
|
81
87
|
textAlign: cfg.titleAlignment,
|
|
82
|
-
marginBottom: subtitle ?
|
|
88
|
+
marginBottom: subtitle ? calculateResponsiveSize(BASE_SPACING.titleMarginBottom, spacingMultiplier) : 0,
|
|
83
89
|
letterSpacing: 0.3,
|
|
84
90
|
},
|
|
85
91
|
subtitle: {
|
|
86
|
-
fontSize: cfg.subtitleFontSize,
|
|
92
|
+
fontSize: calculateResponsiveSize(cfg.subtitleFontSize ?? BASE_FONT_SIZES.subtitle, spacingMultiplier),
|
|
87
93
|
fontWeight: "500",
|
|
88
94
|
color: tokens.colors.textSecondary,
|
|
89
95
|
textAlign: cfg.titleAlignment,
|
|
90
|
-
lineHeight: (cfg.subtitleFontSize ??
|
|
96
|
+
lineHeight: calculateLineHeight(cfg.subtitleFontSize ?? BASE_FONT_SIZES.subtitle, spacingMultiplier),
|
|
91
97
|
opacity: 0.9,
|
|
92
98
|
},
|
|
93
99
|
}),
|
|
94
|
-
[tokens, cfg, subtitle],
|
|
100
|
+
[tokens, cfg, subtitle, spacingMultiplier],
|
|
95
101
|
);
|
|
96
102
|
|
|
97
103
|
return (
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* StepProgress Constants
|
|
3
|
+
*
|
|
4
|
+
* Base size configurations for StepProgress component.
|
|
5
|
+
* All sizes will be multiplied by spacingMultiplier at runtime.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Base spacing (px) - before spacingMultiplier
|
|
10
|
+
*/
|
|
11
|
+
export const BASE_SPACING = {
|
|
12
|
+
gap: 8,
|
|
13
|
+
paddingHorizontal: 24,
|
|
14
|
+
paddingVertical: 16,
|
|
15
|
+
} as const;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Base step dimensions (px) - before spacingMultiplier
|
|
19
|
+
*/
|
|
20
|
+
export const BASE_STEP_DIMENSIONS = {
|
|
21
|
+
height: 4,
|
|
22
|
+
borderRadius: 2,
|
|
23
|
+
} as const;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import React, { useMemo } from "react";
|
|
2
2
|
import { View, StyleSheet, ViewStyle } from "react-native";
|
|
3
3
|
import { useAppDesignTokens } from '../../theme/hooks/useAppDesignTokens';
|
|
4
|
+
import { calculateResponsiveSize } from '../../utils/responsiveUtils';
|
|
5
|
+
import { BASE_SPACING, BASE_STEP_DIMENSIONS } from './StepProgress.constants';
|
|
4
6
|
|
|
5
7
|
export interface StepProgressProps {
|
|
6
8
|
currentStep: number;
|
|
@@ -14,27 +16,28 @@ export const StepProgress: React.FC<StepProgressProps> = ({
|
|
|
14
16
|
style,
|
|
15
17
|
}) => {
|
|
16
18
|
const tokens = useAppDesignTokens();
|
|
19
|
+
const spacingMultiplier = tokens.spacingMultiplier;
|
|
17
20
|
|
|
18
21
|
const styles = useMemo(
|
|
19
22
|
() =>
|
|
20
23
|
StyleSheet.create({
|
|
21
24
|
container: {
|
|
22
25
|
flexDirection: "row",
|
|
23
|
-
gap:
|
|
24
|
-
paddingHorizontal:
|
|
25
|
-
paddingVertical:
|
|
26
|
+
gap: calculateResponsiveSize(BASE_SPACING.gap, spacingMultiplier),
|
|
27
|
+
paddingHorizontal: calculateResponsiveSize(BASE_SPACING.paddingHorizontal, spacingMultiplier),
|
|
28
|
+
paddingVertical: calculateResponsiveSize(BASE_SPACING.paddingVertical, spacingMultiplier),
|
|
26
29
|
},
|
|
27
30
|
step: {
|
|
28
31
|
flex: 1,
|
|
29
|
-
height:
|
|
30
|
-
borderRadius:
|
|
32
|
+
height: calculateResponsiveSize(BASE_STEP_DIMENSIONS.height, spacingMultiplier),
|
|
33
|
+
borderRadius: calculateResponsiveSize(BASE_STEP_DIMENSIONS.borderRadius, spacingMultiplier),
|
|
31
34
|
backgroundColor: tokens.colors.border,
|
|
32
35
|
},
|
|
33
36
|
activeStep: {
|
|
34
37
|
backgroundColor: tokens.colors.primary,
|
|
35
38
|
},
|
|
36
39
|
}),
|
|
37
|
-
[tokens],
|
|
40
|
+
[tokens, spacingMultiplier],
|
|
38
41
|
);
|
|
39
42
|
|
|
40
43
|
return (
|
|
@@ -4,11 +4,12 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import type { AvatarSize, AvatarShape, AvatarType, SizeConfig } from './Avatar.types';
|
|
7
|
+
import { calculateResponsiveSize, calculateResponsiveSizeSubtle } from '../../utils/responsiveUtils';
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
|
-
*
|
|
10
|
+
* Base size configurations (px) - will be multiplied by spacingMultiplier
|
|
10
11
|
*/
|
|
11
|
-
export const
|
|
12
|
+
export const BASE_SIZE_CONFIGS: Record<AvatarSize, SizeConfig> = {
|
|
12
13
|
xs: {
|
|
13
14
|
size: 24,
|
|
14
15
|
fontSize: 10,
|
|
@@ -53,6 +54,23 @@ export const SIZE_CONFIGS: Record<AvatarSize, SizeConfig> = {
|
|
|
53
54
|
},
|
|
54
55
|
};
|
|
55
56
|
|
|
57
|
+
/**
|
|
58
|
+
* Get responsive size configurations
|
|
59
|
+
* Multiplies base sizes by spacingMultiplier for tablet/small device support
|
|
60
|
+
*/
|
|
61
|
+
export const getSizeConfigs = (spacingMultiplier: number): Record<AvatarSize, SizeConfig> => {
|
|
62
|
+
return Object.entries(BASE_SIZE_CONFIGS).reduce((acc, [key, config]) => {
|
|
63
|
+
acc[key as AvatarSize] = {
|
|
64
|
+
size: calculateResponsiveSize(config.size, spacingMultiplier),
|
|
65
|
+
fontSize: calculateResponsiveSize(config.fontSize, spacingMultiplier),
|
|
66
|
+
iconSize: calculateResponsiveSize(config.iconSize, spacingMultiplier),
|
|
67
|
+
statusSize: calculateResponsiveSize(config.statusSize, spacingMultiplier),
|
|
68
|
+
borderWidth: calculateResponsiveSizeSubtle(config.borderWidth, spacingMultiplier),
|
|
69
|
+
};
|
|
70
|
+
return acc;
|
|
71
|
+
}, {} as Record<AvatarSize, SizeConfig>);
|
|
72
|
+
};
|
|
73
|
+
|
|
56
74
|
/**
|
|
57
75
|
* Avatar background colors
|
|
58
76
|
* Vibrant, accessible colors with good contrast
|
|
@@ -10,7 +10,8 @@ import { View, Image, StyleSheet, TouchableOpacity, type StyleProp, type ViewSty
|
|
|
10
10
|
import { useAppDesignTokens } from '../../theme';
|
|
11
11
|
import { AtomicText, AtomicIcon } from '../../atoms';
|
|
12
12
|
import type { AvatarSize, AvatarShape } from './Avatar.types';
|
|
13
|
-
import {
|
|
13
|
+
import type { SizeConfig } from './Avatar.types';
|
|
14
|
+
import { getSizeConfigs, AVATAR_CONSTANTS } from './Avatar.constants';
|
|
14
15
|
import { AvatarUtils } from './Avatar.utils';
|
|
15
16
|
|
|
16
17
|
export interface AvatarProps {
|
|
@@ -33,7 +34,7 @@ interface AvatarContentProps {
|
|
|
33
34
|
uri?: string;
|
|
34
35
|
initials: string;
|
|
35
36
|
icon: string;
|
|
36
|
-
config:
|
|
37
|
+
config: SizeConfig;
|
|
37
38
|
borderRadius: number;
|
|
38
39
|
imageStyle?: StyleProp<ImageStyle>;
|
|
39
40
|
}
|
|
@@ -107,7 +108,8 @@ export const Avatar: React.FC<AvatarProps> = ({
|
|
|
107
108
|
onPress,
|
|
108
109
|
}) => {
|
|
109
110
|
const tokens = useAppDesignTokens();
|
|
110
|
-
const
|
|
111
|
+
const sizeConfigs = useMemo(() => getSizeConfigs(tokens.spacingMultiplier), [tokens.spacingMultiplier]);
|
|
112
|
+
const config = useMemo(() => sizeConfigs[size], [size, sizeConfigs]);
|
|
111
113
|
|
|
112
114
|
const hasImage = !!uri;
|
|
113
115
|
const hasName = !!name;
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import type { AvatarSize, AvatarShape, AvatarConfig, SizeConfig } from './Avatar.types';
|
|
8
|
-
import { AVATAR_COLORS, STATUS_COLORS, SHAPE_CONFIGS,
|
|
8
|
+
import { AVATAR_COLORS, STATUS_COLORS, SHAPE_CONFIGS, BASE_SIZE_CONFIGS } from './Avatar.constants';
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Avatar utility class
|
|
@@ -71,10 +71,10 @@ export class AvatarUtils {
|
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
/**
|
|
74
|
-
* Get size config
|
|
74
|
+
* Get base size config (before spacingMultiplier)
|
|
75
75
|
*/
|
|
76
|
-
static
|
|
77
|
-
return
|
|
76
|
+
static getBaseSizeConfig(size: AvatarSize): SizeConfig {
|
|
77
|
+
return BASE_SIZE_CONFIGS[size] ?? BASE_SIZE_CONFIGS.md;
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
/**
|
|
@@ -11,7 +11,7 @@ import { useAppDesignTokens } from '../../theme';
|
|
|
11
11
|
import { AtomicText } from '../../atoms';
|
|
12
12
|
import { Avatar } from './Avatar';
|
|
13
13
|
import type { AvatarSize, AvatarShape } from './Avatar.types';
|
|
14
|
-
import {
|
|
14
|
+
import { BASE_SIZE_CONFIGS, AVATAR_CONSTANTS } from './Avatar.constants';
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
17
|
* Avatar item for group
|
|
@@ -138,7 +138,7 @@ export const AvatarGroup: React.FC<AvatarGroupProps> = React.memo(({
|
|
|
138
138
|
style,
|
|
139
139
|
}) => {
|
|
140
140
|
const tokens = useAppDesignTokens();
|
|
141
|
-
const config =
|
|
141
|
+
const config = BASE_SIZE_CONFIGS[size];
|
|
142
142
|
|
|
143
143
|
// Memoize calculations to prevent recalculation on every render
|
|
144
144
|
const { visibleItems, overflowCount, hasOverflow } = useMemo(() => {
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import type { ViewStyle, TextStyle } from 'react-native';
|
|
5
5
|
import type { DesignTokens } from '../../../theme';
|
|
6
|
-
import { isTablet } from '../../../device/detection';
|
|
7
6
|
|
|
8
7
|
export interface ListItemStyles {
|
|
9
8
|
container: ViewStyle;
|
|
@@ -14,8 +13,8 @@ export interface ListItemStyles {
|
|
|
14
13
|
}
|
|
15
14
|
|
|
16
15
|
export const getListItemStyles = (tokens: DesignTokens): ListItemStyles => {
|
|
17
|
-
// Responsive minHeight:
|
|
18
|
-
const minHeight =
|
|
16
|
+
// Responsive minHeight: uses base height (56) multiplied by spacingMultiplier
|
|
17
|
+
const minHeight = Math.floor(56 * tokens.spacingMultiplier);
|
|
19
18
|
|
|
20
19
|
return {
|
|
21
20
|
container: {
|
|
@@ -22,21 +22,24 @@ import { useEffect } from "react";
|
|
|
22
22
|
* ```
|
|
23
23
|
*/
|
|
24
24
|
export function useAppFocusEffect(effect: () => void | (() => void)): void {
|
|
25
|
+
// Always run the effect - if navigation is ready, useFocusEffect will handle it
|
|
26
|
+
// Otherwise, useEffect will run it once
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
const cleanup = effect();
|
|
29
|
+
return () => {
|
|
30
|
+
if (typeof cleanup === 'function') {
|
|
31
|
+
cleanup();
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
}, [effect]);
|
|
35
|
+
|
|
36
|
+
// Try to use React Navigation's useFocusEffect if available
|
|
25
37
|
try {
|
|
26
|
-
// Try to use React Navigation's useFocusEffect
|
|
27
38
|
useRNFocusEffect(effect);
|
|
28
|
-
} catch (
|
|
29
|
-
// Navigation not ready -
|
|
39
|
+
} catch (_error) {
|
|
40
|
+
// Navigation not ready - useEffect above handles it
|
|
30
41
|
if (__DEV__) {
|
|
31
42
|
console.warn('[useAppFocusEffect] Navigation not ready. Running effect once instead.');
|
|
32
43
|
}
|
|
33
|
-
useEffect(() => {
|
|
34
|
-
const cleanup = effect();
|
|
35
|
-
return () => {
|
|
36
|
-
if (typeof cleanup === 'function') {
|
|
37
|
-
cleanup();
|
|
38
|
-
}
|
|
39
|
-
};
|
|
40
|
-
}, [effect]);
|
|
41
44
|
}
|
|
42
45
|
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { useIsFocused as useRNIsFocused } from "@react-navigation/native";
|
|
2
|
-
import { useMemo } from "react";
|
|
3
2
|
|
|
4
3
|
/**
|
|
5
4
|
* useAppIsFocused Hook
|
|
@@ -18,7 +17,7 @@ import { useMemo } from "react";
|
|
|
18
17
|
export function useAppIsFocused(): boolean {
|
|
19
18
|
try {
|
|
20
19
|
return useRNIsFocused();
|
|
21
|
-
} catch (
|
|
20
|
+
} catch (_error) {
|
|
22
21
|
// Navigation not ready - return false
|
|
23
22
|
if (__DEV__) {
|
|
24
23
|
console.warn('[useAppIsFocused] Navigation not ready. Returning false.');
|