@umituz/react-native-design-system 2.6.61 → 2.6.62
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/AtomicButton.tsx +6 -257
- package/src/atoms/AtomicChip.tsx +4 -224
- package/src/atoms/button/AtomicButton.tsx +108 -0
- package/src/atoms/button/configs/buttonSizeConfig.ts +37 -0
- package/src/atoms/button/index.ts +6 -0
- package/src/atoms/button/styles/buttonStyles.ts +36 -0
- package/src/atoms/button/styles/buttonVariantStyles.ts +88 -0
- package/src/atoms/button/types/index.ts +40 -0
- package/src/atoms/chip/AtomicChip.tsx +112 -0
- package/src/atoms/chip/configs/chipColorConfig.ts +47 -0
- package/src/atoms/chip/configs/chipSizeConfig.ts +34 -0
- package/src/atoms/chip/index.ts +6 -0
- package/src/atoms/chip/styles/chipStyles.ts +28 -0
- package/src/atoms/chip/types/index.ts +42 -0
- package/src/layouts/ScreenLayout/ScreenLayout.tsx +17 -181
- package/src/layouts/ScreenLayout/components/ContentWrapper.tsx +31 -0
- package/src/layouts/ScreenLayout/components/index.ts +6 -0
- package/src/layouts/ScreenLayout/styles/screenLayoutStyles.ts +47 -0
- package/src/layouts/ScreenLayout/types/index.ts +27 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Button Variant Styles
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { DesignTokens } from '../../../theme';
|
|
6
|
+
import type { ButtonVariant, ButtonVariantStyles } from '../types';
|
|
7
|
+
|
|
8
|
+
export const getVariantStyles = (
|
|
9
|
+
variant: ButtonVariant,
|
|
10
|
+
tokens: DesignTokens,
|
|
11
|
+
): ButtonVariantStyles => {
|
|
12
|
+
const baseStyle = {
|
|
13
|
+
backgroundColor: tokens.colors.primary,
|
|
14
|
+
borderWidth: 0,
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const baseTextStyle = {
|
|
18
|
+
color: tokens.colors.textInverse,
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
switch (variant) {
|
|
22
|
+
case 'primary':
|
|
23
|
+
return {
|
|
24
|
+
container: {
|
|
25
|
+
...baseStyle,
|
|
26
|
+
backgroundColor: tokens.colors.primary,
|
|
27
|
+
},
|
|
28
|
+
text: {
|
|
29
|
+
...baseTextStyle,
|
|
30
|
+
color: tokens.colors.textInverse,
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
case 'secondary':
|
|
35
|
+
return {
|
|
36
|
+
container: {
|
|
37
|
+
...baseStyle,
|
|
38
|
+
backgroundColor: tokens.colors.surfaceSecondary,
|
|
39
|
+
},
|
|
40
|
+
text: {
|
|
41
|
+
...baseTextStyle,
|
|
42
|
+
color: tokens.colors.textPrimary,
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
case 'outline':
|
|
47
|
+
return {
|
|
48
|
+
container: {
|
|
49
|
+
backgroundColor: undefined,
|
|
50
|
+
borderWidth: 1,
|
|
51
|
+
borderColor: tokens.colors.border,
|
|
52
|
+
},
|
|
53
|
+
text: {
|
|
54
|
+
...baseTextStyle,
|
|
55
|
+
color: tokens.colors.textPrimary,
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
case 'text':
|
|
60
|
+
return {
|
|
61
|
+
container: {
|
|
62
|
+
backgroundColor: undefined,
|
|
63
|
+
},
|
|
64
|
+
text: {
|
|
65
|
+
...baseTextStyle,
|
|
66
|
+
color: tokens.colors.primary,
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
case 'danger':
|
|
71
|
+
return {
|
|
72
|
+
container: {
|
|
73
|
+
...baseStyle,
|
|
74
|
+
backgroundColor: tokens.colors.error,
|
|
75
|
+
},
|
|
76
|
+
text: {
|
|
77
|
+
...baseTextStyle,
|
|
78
|
+
color: tokens.colors.textInverse,
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
default:
|
|
83
|
+
return {
|
|
84
|
+
container: baseStyle,
|
|
85
|
+
text: baseTextStyle,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AtomicButton Type Definitions
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { StyleProp } from 'react-native';
|
|
6
|
+
import type { TextStyle, ViewStyle } from 'react-native';
|
|
7
|
+
import type { IconName } from '../../AtomicIcon';
|
|
8
|
+
|
|
9
|
+
export type ButtonVariant = 'primary' | 'secondary' | 'outline' | 'text' | 'danger';
|
|
10
|
+
export type ButtonSize = 'sm' | 'md' | 'lg';
|
|
11
|
+
|
|
12
|
+
export interface AtomicButtonProps {
|
|
13
|
+
readonly title?: string;
|
|
14
|
+
readonly children?: React.ReactNode;
|
|
15
|
+
readonly onPress: () => void;
|
|
16
|
+
readonly variant?: ButtonVariant;
|
|
17
|
+
readonly size?: ButtonSize;
|
|
18
|
+
readonly disabled?: boolean;
|
|
19
|
+
readonly loading?: boolean;
|
|
20
|
+
readonly icon?: IconName;
|
|
21
|
+
readonly iconPosition?: 'left' | 'right';
|
|
22
|
+
readonly fullWidth?: boolean;
|
|
23
|
+
readonly style?: StyleProp<ViewStyle>;
|
|
24
|
+
readonly textStyle?: StyleProp<TextStyle>;
|
|
25
|
+
readonly activeOpacity?: number;
|
|
26
|
+
readonly testID?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface ButtonSizeConfig {
|
|
30
|
+
readonly paddingVertical: number;
|
|
31
|
+
readonly paddingHorizontal: number;
|
|
32
|
+
readonly fontSize: number;
|
|
33
|
+
readonly iconSize: number;
|
|
34
|
+
readonly minHeight: number;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface ButtonVariantStyles {
|
|
38
|
+
readonly container: ViewStyle;
|
|
39
|
+
readonly text: TextStyle;
|
|
40
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AtomicChip Component
|
|
3
|
+
* Refactored: Extracted configs, styles, and types
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React from 'react';
|
|
7
|
+
import { View, ViewStyle, TouchableOpacity } from 'react-native';
|
|
8
|
+
import { AtomicText } from '../AtomicText';
|
|
9
|
+
import { AtomicIcon } from '../AtomicIcon';
|
|
10
|
+
import { useAppDesignTokens } from '../../theme';
|
|
11
|
+
import { getChipSizeConfig } from './configs/chipSizeConfig';
|
|
12
|
+
import { getChipColorConfig } from './configs/chipColorConfig';
|
|
13
|
+
import { getChipBorderStyle, getChipSelectedStyle } from './styles/chipStyles';
|
|
14
|
+
import type { AtomicChipProps } from './types';
|
|
15
|
+
|
|
16
|
+
export const AtomicChip: React.FC<AtomicChipProps> = React.memo(({
|
|
17
|
+
children,
|
|
18
|
+
variant = 'filled',
|
|
19
|
+
size = 'md',
|
|
20
|
+
color = 'primary',
|
|
21
|
+
backgroundColor,
|
|
22
|
+
textColor,
|
|
23
|
+
borderColor,
|
|
24
|
+
leadingIcon,
|
|
25
|
+
trailingIcon,
|
|
26
|
+
clickable = false,
|
|
27
|
+
onPress,
|
|
28
|
+
selected = false,
|
|
29
|
+
disabled = false,
|
|
30
|
+
style,
|
|
31
|
+
testID,
|
|
32
|
+
activeOpacity = 0.7,
|
|
33
|
+
}) => {
|
|
34
|
+
const tokens = useAppDesignTokens();
|
|
35
|
+
|
|
36
|
+
const sizeConfig = getChipSizeConfig(size, tokens);
|
|
37
|
+
const colorConfig = getChipColorConfig(color, variant, tokens);
|
|
38
|
+
const borderStyle = getChipBorderStyle(variant, tokens);
|
|
39
|
+
const selectedStyle = getChipSelectedStyle(selected, tokens);
|
|
40
|
+
|
|
41
|
+
// Apply custom colors if provided
|
|
42
|
+
const finalBackgroundColor = backgroundColor || colorConfig.bg;
|
|
43
|
+
const finalTextColor = textColor || colorConfig.text;
|
|
44
|
+
const finalBorderColor = borderColor || colorConfig.border;
|
|
45
|
+
|
|
46
|
+
// Handle disabled state
|
|
47
|
+
const isDisabled = disabled || (!clickable && !onPress);
|
|
48
|
+
const opacity = isDisabled ? 0.5 : 1;
|
|
49
|
+
|
|
50
|
+
const chipStyle: ViewStyle = {
|
|
51
|
+
flexDirection: 'row',
|
|
52
|
+
alignItems: 'center',
|
|
53
|
+
justifyContent: 'center',
|
|
54
|
+
paddingHorizontal: sizeConfig.paddingHorizontal,
|
|
55
|
+
paddingVertical: sizeConfig.paddingVertical,
|
|
56
|
+
backgroundColor: finalBackgroundColor,
|
|
57
|
+
opacity,
|
|
58
|
+
...borderStyle,
|
|
59
|
+
borderColor: finalBorderColor,
|
|
60
|
+
...selectedStyle,
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const textStyle = {
|
|
64
|
+
fontSize: sizeConfig.fontSize,
|
|
65
|
+
fontWeight: tokens.typography.medium,
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const iconColor = finalTextColor;
|
|
69
|
+
|
|
70
|
+
const content = (
|
|
71
|
+
<View style={[chipStyle, style]} testID={testID}>
|
|
72
|
+
{leadingIcon && (
|
|
73
|
+
<AtomicIcon
|
|
74
|
+
name={leadingIcon}
|
|
75
|
+
size={sizeConfig.iconSize}
|
|
76
|
+
customColor={iconColor}
|
|
77
|
+
style={{ marginRight: tokens.spacing.xs }}
|
|
78
|
+
/>
|
|
79
|
+
)}
|
|
80
|
+
<AtomicText
|
|
81
|
+
type="labelMedium"
|
|
82
|
+
color={finalTextColor}
|
|
83
|
+
style={textStyle}
|
|
84
|
+
>
|
|
85
|
+
{children}
|
|
86
|
+
</AtomicText>
|
|
87
|
+
{trailingIcon && (
|
|
88
|
+
<AtomicIcon
|
|
89
|
+
name={trailingIcon}
|
|
90
|
+
size={sizeConfig.iconSize}
|
|
91
|
+
customColor={iconColor}
|
|
92
|
+
style={{ marginLeft: tokens.spacing.xs }}
|
|
93
|
+
/>
|
|
94
|
+
)}
|
|
95
|
+
</View>
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
if (clickable && onPress && !disabled) {
|
|
99
|
+
return (
|
|
100
|
+
<TouchableOpacity onPress={onPress} activeOpacity={activeOpacity}>
|
|
101
|
+
{content}
|
|
102
|
+
</TouchableOpacity>
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return content;
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
AtomicChip.displayName = 'AtomicChip';
|
|
110
|
+
|
|
111
|
+
// Re-export types for convenience
|
|
112
|
+
export type { AtomicChipProps, ChipVariant, ChipSize, ChipColor } from './types';
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chip Color Configuration
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { DesignTokens } from '../../../theme';
|
|
6
|
+
import type { ChipColor, ChipVariant, ChipColorConfig } from '../types';
|
|
7
|
+
|
|
8
|
+
export const getChipColorConfig = (
|
|
9
|
+
color: ChipColor,
|
|
10
|
+
variant: ChipVariant,
|
|
11
|
+
tokens: DesignTokens,
|
|
12
|
+
): ChipColorConfig => {
|
|
13
|
+
const colorMap: Record<ChipColor, Record<ChipVariant, ChipColorConfig>> = {
|
|
14
|
+
primary: {
|
|
15
|
+
filled: { bg: tokens.colors.primary, text: tokens.colors.onPrimary, border: tokens.colors.primary },
|
|
16
|
+
outlined: { bg: undefined, text: tokens.colors.primary, border: tokens.colors.primary },
|
|
17
|
+
soft: { bg: tokens.colors.primaryContainer, text: tokens.colors.onPrimaryContainer, border: undefined },
|
|
18
|
+
},
|
|
19
|
+
secondary: {
|
|
20
|
+
filled: { bg: tokens.colors.secondary, text: tokens.colors.onSecondary, border: tokens.colors.secondary },
|
|
21
|
+
outlined: { bg: undefined, text: tokens.colors.secondary, border: tokens.colors.secondary },
|
|
22
|
+
soft: { bg: tokens.colors.secondaryContainer, text: tokens.colors.onSecondaryContainer, border: undefined },
|
|
23
|
+
},
|
|
24
|
+
success: {
|
|
25
|
+
filled: { bg: tokens.colors.success, text: tokens.colors.onSuccess, border: tokens.colors.success },
|
|
26
|
+
outlined: { bg: undefined, text: tokens.colors.success, border: tokens.colors.success },
|
|
27
|
+
soft: { bg: tokens.colors.successContainer, text: tokens.colors.onSuccessContainer, border: undefined },
|
|
28
|
+
},
|
|
29
|
+
warning: {
|
|
30
|
+
filled: { bg: tokens.colors.warning, text: tokens.colors.onWarning, border: tokens.colors.warning },
|
|
31
|
+
outlined: { bg: undefined, text: tokens.colors.warning, border: tokens.colors.warning },
|
|
32
|
+
soft: { bg: tokens.colors.warningContainer, text: tokens.colors.onWarningContainer, border: undefined },
|
|
33
|
+
},
|
|
34
|
+
error: {
|
|
35
|
+
filled: { bg: tokens.colors.error, text: tokens.colors.onError, border: tokens.colors.error },
|
|
36
|
+
outlined: { bg: undefined, text: tokens.colors.error, border: tokens.colors.error },
|
|
37
|
+
soft: { bg: tokens.colors.errorContainer, text: tokens.colors.onErrorContainer, border: undefined },
|
|
38
|
+
},
|
|
39
|
+
info: {
|
|
40
|
+
filled: { bg: tokens.colors.info, text: tokens.colors.onInfo, border: tokens.colors.info },
|
|
41
|
+
outlined: { bg: undefined, text: tokens.colors.info, border: tokens.colors.info },
|
|
42
|
+
soft: { bg: tokens.colors.infoContainer, text: tokens.colors.onInfoContainer, border: undefined },
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
return colorMap[color][variant];
|
|
47
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chip Size Configuration
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { DesignTokens } from '../../../theme';
|
|
6
|
+
import type { ChipSize, ChipSizeConfig } from '../types';
|
|
7
|
+
|
|
8
|
+
export const getChipSizeConfig = (
|
|
9
|
+
size: ChipSize,
|
|
10
|
+
tokens: DesignTokens,
|
|
11
|
+
): ChipSizeConfig => {
|
|
12
|
+
const sizeConfigs: Record<ChipSize, ChipSizeConfig> = {
|
|
13
|
+
sm: {
|
|
14
|
+
paddingHorizontal: tokens.spacing.sm,
|
|
15
|
+
paddingVertical: tokens.spacing.xs,
|
|
16
|
+
fontSize: tokens.typography.bodySmall.responsiveFontSize,
|
|
17
|
+
iconSize: 'xs',
|
|
18
|
+
},
|
|
19
|
+
md: {
|
|
20
|
+
paddingHorizontal: tokens.spacing.md,
|
|
21
|
+
paddingVertical: tokens.spacing.sm,
|
|
22
|
+
fontSize: tokens.typography.bodyMedium.responsiveFontSize,
|
|
23
|
+
iconSize: 'sm',
|
|
24
|
+
},
|
|
25
|
+
lg: {
|
|
26
|
+
paddingHorizontal: tokens.spacing.md,
|
|
27
|
+
paddingVertical: tokens.spacing.sm,
|
|
28
|
+
fontSize: tokens.typography.bodyLarge.responsiveFontSize,
|
|
29
|
+
iconSize: 'sm',
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
return sizeConfigs[size];
|
|
34
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chip Styles
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { DesignTokens } from '../../../theme';
|
|
6
|
+
import type { ChipVariant } from '../types';
|
|
7
|
+
|
|
8
|
+
export const getChipBorderStyle = (
|
|
9
|
+
variant: ChipVariant,
|
|
10
|
+
tokens: DesignTokens,
|
|
11
|
+
) => {
|
|
12
|
+
return {
|
|
13
|
+
borderWidth: variant === 'outlined' ? 1 : 0,
|
|
14
|
+
borderRadius: tokens.borders.radius.xl,
|
|
15
|
+
};
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export const getChipSelectedStyle = (
|
|
19
|
+
selected: boolean,
|
|
20
|
+
tokens: DesignTokens,
|
|
21
|
+
) => {
|
|
22
|
+
if (!selected) return {};
|
|
23
|
+
|
|
24
|
+
return {
|
|
25
|
+
borderWidth: tokens.borders.width.medium,
|
|
26
|
+
borderColor: tokens.colors.primary,
|
|
27
|
+
};
|
|
28
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AtomicChip Type Definitions
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { ViewStyle } from 'react-native';
|
|
6
|
+
import type { IconSize } from '../../AtomicIcon';
|
|
7
|
+
|
|
8
|
+
export type ChipVariant = 'filled' | 'outlined' | 'soft';
|
|
9
|
+
export type ChipSize = 'sm' | 'md' | 'lg';
|
|
10
|
+
export type ChipColor = 'primary' | 'secondary' | 'success' | 'warning' | 'error' | 'info';
|
|
11
|
+
|
|
12
|
+
export interface AtomicChipProps {
|
|
13
|
+
readonly children: React.ReactNode;
|
|
14
|
+
readonly variant?: ChipVariant;
|
|
15
|
+
readonly size?: ChipSize;
|
|
16
|
+
readonly color?: ChipColor;
|
|
17
|
+
readonly backgroundColor?: string;
|
|
18
|
+
readonly textColor?: string;
|
|
19
|
+
readonly borderColor?: string;
|
|
20
|
+
readonly leadingIcon?: string;
|
|
21
|
+
readonly trailingIcon?: string;
|
|
22
|
+
readonly clickable?: boolean;
|
|
23
|
+
readonly onPress?: () => void;
|
|
24
|
+
readonly selected?: boolean;
|
|
25
|
+
readonly disabled?: boolean;
|
|
26
|
+
readonly style?: ViewStyle;
|
|
27
|
+
readonly testID?: string;
|
|
28
|
+
readonly activeOpacity?: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface ChipSizeConfig {
|
|
32
|
+
readonly paddingHorizontal: number;
|
|
33
|
+
readonly paddingVertical: number;
|
|
34
|
+
readonly fontSize: number;
|
|
35
|
+
readonly iconSize: IconSize;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface ChipColorConfig {
|
|
39
|
+
readonly bg: string | undefined;
|
|
40
|
+
readonly text: string;
|
|
41
|
+
readonly border: string | undefined;
|
|
42
|
+
}
|
|
@@ -1,145 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* ScreenLayout
|
|
3
|
-
*
|
|
4
|
-
* Provides consistent layout structure for all screens:
|
|
5
|
-
* - SafeAreaView with configurable edges
|
|
6
|
-
* - Optional ScrollView for content
|
|
7
|
-
* - Theme-aware background colors
|
|
8
|
-
* - Optional header/footer slots
|
|
9
|
-
* - Consistent spacing and padding
|
|
10
|
-
*
|
|
11
|
-
* Usage:
|
|
12
|
-
* <ScreenLayout>
|
|
13
|
-
* <View>Your content here</View>
|
|
14
|
-
* </ScreenLayout>
|
|
15
|
-
*
|
|
16
|
-
* Advanced:
|
|
17
|
-
* <ScreenLayout
|
|
18
|
-
* scrollable={false}
|
|
19
|
-
* edges={['top', 'bottom']}
|
|
20
|
-
* header={<CustomHeader />}
|
|
21
|
-
* >
|
|
22
|
-
* <View>Content</View>
|
|
23
|
-
* </ScreenLayout>
|
|
2
|
+
* ScreenLayout Component
|
|
3
|
+
* Refactored: Extracted types, styles, and ContentWrapper
|
|
24
4
|
*/
|
|
25
5
|
|
|
26
6
|
import React, { useMemo } from 'react';
|
|
27
|
-
import { View, ScrollView
|
|
28
|
-
import { SafeAreaView, useSafeAreaInsets
|
|
7
|
+
import { View, ScrollView } from 'react-native';
|
|
8
|
+
import { SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
29
9
|
import { useAppDesignTokens } from '../../theme';
|
|
30
10
|
import { getScreenLayoutConfig } from '../../responsive/responsiveLayout';
|
|
31
|
-
import {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
* NOTE: This component now works in conjunction with the SafeAreaProvider
|
|
35
|
-
* from our safe-area module. The SafeAreaProvider should wrap your app root:
|
|
36
|
-
*
|
|
37
|
-
* import { SafeAreaProvider } from '../../index';
|
|
38
|
-
*
|
|
39
|
-
* function App() {
|
|
40
|
-
* return (
|
|
41
|
-
* <SafeAreaProvider>
|
|
42
|
-
* <YourApp />
|
|
43
|
-
* </SafeAreaProvider>
|
|
44
|
-
* );
|
|
45
|
-
* }
|
|
46
|
-
*/
|
|
47
|
-
|
|
48
|
-
export interface ScreenLayoutProps {
|
|
49
|
-
/**
|
|
50
|
-
* Content to render inside the layout
|
|
51
|
-
*/
|
|
52
|
-
children: React.ReactNode;
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Enable scrolling (default: true)
|
|
56
|
-
* Set to false for screens with custom scroll logic
|
|
57
|
-
*/
|
|
58
|
-
scrollable?: boolean;
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Safe area edges to apply (default: ['top'])
|
|
62
|
-
* Common values:
|
|
63
|
-
* - ['top'] - For screens with bottom tabs
|
|
64
|
-
* - ['top', 'bottom'] - For modal screens
|
|
65
|
-
* - [] - No safe area (use cautiously)
|
|
66
|
-
*/
|
|
67
|
-
edges?: Edge[];
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Optional header component
|
|
71
|
-
* Rendered above scrollable content
|
|
72
|
-
*/
|
|
73
|
-
header?: React.ReactNode;
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Optional footer component
|
|
77
|
-
* Rendered below scrollable content
|
|
78
|
-
*/
|
|
79
|
-
footer?: React.ReactNode;
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Override background color
|
|
83
|
-
* If not provided, uses theme's backgroundPrimary
|
|
84
|
-
*/
|
|
85
|
-
backgroundColor?: string;
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Custom container style
|
|
89
|
-
*/
|
|
90
|
-
containerStyle?: ViewStyle;
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Custom content container style (for ScrollView)
|
|
94
|
-
*/
|
|
95
|
-
contentContainerStyle?: ViewStyle;
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Test ID for automation
|
|
99
|
-
*/
|
|
100
|
-
testID?: string;
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* Hide vertical scroll indicator (default: false)
|
|
104
|
-
*/
|
|
105
|
-
hideScrollIndicator?: boolean;
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* Enable keyboard avoiding behavior (default: false)
|
|
109
|
-
* Useful for screens with inputs
|
|
110
|
-
*/
|
|
111
|
-
keyboardAvoiding?: boolean;
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* Accessibility label for the layout
|
|
115
|
-
*/
|
|
116
|
-
accessibilityLabel?: string;
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* Accessibility hint for the layout
|
|
120
|
-
*/
|
|
121
|
-
accessibilityHint?: string;
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* Whether the layout is accessible
|
|
125
|
-
*/
|
|
126
|
-
accessible?: boolean;
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* Enable responsive content width and centering (default: true)
|
|
130
|
-
*/
|
|
131
|
-
responsiveEnabled?: boolean;
|
|
132
|
-
|
|
133
|
-
/**
|
|
134
|
-
* Maximum content width override
|
|
135
|
-
*/
|
|
136
|
-
maxWidth?: number;
|
|
137
|
-
|
|
138
|
-
/**
|
|
139
|
-
* RefreshControl component for pull-to-refresh
|
|
140
|
-
*/
|
|
141
|
-
refreshControl?: React.ReactElement<RefreshControlProps>;
|
|
142
|
-
}
|
|
11
|
+
import { ContentWrapper } from './components/ContentWrapper';
|
|
12
|
+
import { getScreenLayoutStyles } from './styles/screenLayoutStyles';
|
|
13
|
+
import type { ScreenLayoutProps } from './types';
|
|
143
14
|
|
|
144
15
|
export const ScreenLayout: React.FC<ScreenLayoutProps> = ({
|
|
145
16
|
children,
|
|
@@ -157,7 +28,6 @@ export const ScreenLayout: React.FC<ScreenLayoutProps> = ({
|
|
|
157
28
|
maxWidth,
|
|
158
29
|
refreshControl,
|
|
159
30
|
}) => {
|
|
160
|
-
// Automatically uses current theme from global store
|
|
161
31
|
const tokens = useAppDesignTokens();
|
|
162
32
|
const insets = useSafeAreaInsets();
|
|
163
33
|
|
|
@@ -172,51 +42,14 @@ export const ScreenLayout: React.FC<ScreenLayoutProps> = ({
|
|
|
172
42
|
const horizontalPadding = responsiveEnabled ? layoutConfig.horizontalPadding : tokens.spacing.md;
|
|
173
43
|
const verticalPadding = responsiveEnabled ? layoutConfig.verticalPadding : tokens.spacing.lg;
|
|
174
44
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
flex: 1,
|
|
181
|
-
},
|
|
182
|
-
responsiveWrapper: {
|
|
183
|
-
flex: 1,
|
|
184
|
-
width: '100%',
|
|
185
|
-
...(finalMaxWidth ? { maxWidth: finalMaxWidth, alignSelf: 'center' as const } : {}),
|
|
186
|
-
},
|
|
187
|
-
content: {
|
|
188
|
-
flex: 1,
|
|
189
|
-
paddingTop: verticalPadding,
|
|
190
|
-
paddingHorizontal: horizontalPadding,
|
|
191
|
-
},
|
|
192
|
-
scrollView: {
|
|
193
|
-
flex: 1,
|
|
194
|
-
},
|
|
195
|
-
scrollContent: {
|
|
196
|
-
flexGrow: 1,
|
|
197
|
-
paddingTop: verticalPadding,
|
|
198
|
-
paddingHorizontal: horizontalPadding,
|
|
199
|
-
paddingBottom: verticalPadding,
|
|
200
|
-
},
|
|
201
|
-
}), [tokens, finalMaxWidth, horizontalPadding, verticalPadding]);
|
|
45
|
+
// Pre-compute styles
|
|
46
|
+
const styles = useMemo(
|
|
47
|
+
() => getScreenLayoutStyles(tokens, { maxWidth: finalMaxWidth, horizontalPadding, verticalPadding }),
|
|
48
|
+
[tokens, finalMaxWidth, horizontalPadding, verticalPadding]
|
|
49
|
+
);
|
|
202
50
|
|
|
203
51
|
const bgColor = backgroundColor || tokens.colors.backgroundPrimary;
|
|
204
52
|
|
|
205
|
-
// Content wrapper - optionally with KeyboardAvoidingView
|
|
206
|
-
// Uses 'padding' behavior which works consistently cross-platform
|
|
207
|
-
const ContentWrapper: React.FC<{ children: React.ReactNode }> = ({ children: wrapperChildren }) => {
|
|
208
|
-
if (keyboardAvoiding) {
|
|
209
|
-
return (
|
|
210
|
-
<AtomicKeyboardAvoidingView
|
|
211
|
-
style={styles.keyboardAvoidingView}
|
|
212
|
-
>
|
|
213
|
-
{wrapperChildren}
|
|
214
|
-
</AtomicKeyboardAvoidingView>
|
|
215
|
-
);
|
|
216
|
-
}
|
|
217
|
-
return <>{wrapperChildren}</>;
|
|
218
|
-
};
|
|
219
|
-
|
|
220
53
|
// Non-scrollable layout
|
|
221
54
|
if (!scrollable) {
|
|
222
55
|
return (
|
|
@@ -225,7 +58,7 @@ export const ScreenLayout: React.FC<ScreenLayoutProps> = ({
|
|
|
225
58
|
edges={edges}
|
|
226
59
|
testID={testID}
|
|
227
60
|
>
|
|
228
|
-
<ContentWrapper>
|
|
61
|
+
<ContentWrapper keyboardAvoiding={keyboardAvoiding}>
|
|
229
62
|
<View style={styles.responsiveWrapper}>
|
|
230
63
|
{header}
|
|
231
64
|
<View style={[styles.content, contentContainerStyle]}>
|
|
@@ -245,7 +78,7 @@ export const ScreenLayout: React.FC<ScreenLayoutProps> = ({
|
|
|
245
78
|
edges={edges}
|
|
246
79
|
testID={testID}
|
|
247
80
|
>
|
|
248
|
-
<ContentWrapper>
|
|
81
|
+
<ContentWrapper keyboardAvoiding={keyboardAvoiding}>
|
|
249
82
|
<View style={styles.responsiveWrapper}>
|
|
250
83
|
{header}
|
|
251
84
|
<ScrollView
|
|
@@ -263,3 +96,6 @@ export const ScreenLayout: React.FC<ScreenLayoutProps> = ({
|
|
|
263
96
|
</SafeAreaView>
|
|
264
97
|
);
|
|
265
98
|
};
|
|
99
|
+
|
|
100
|
+
// Re-export types for convenience
|
|
101
|
+
export type { ScreenLayoutProps } from './types';
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ContentWrapper Component
|
|
3
|
+
* Conditionally wraps content with KeyboardAvoidingView
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React from 'react';
|
|
7
|
+
import { View } from 'react-native';
|
|
8
|
+
import type { ViewStyle } from 'react-native';
|
|
9
|
+
import { AtomicKeyboardAvoidingView } from '../../../atoms';
|
|
10
|
+
|
|
11
|
+
export interface ContentWrapperProps {
|
|
12
|
+
readonly children: React.ReactNode;
|
|
13
|
+
readonly keyboardAvoiding?: boolean;
|
|
14
|
+
readonly style?: ViewStyle;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const ContentWrapper: React.FC<ContentWrapperProps> = ({
|
|
18
|
+
children,
|
|
19
|
+
keyboardAvoiding = false,
|
|
20
|
+
style,
|
|
21
|
+
}) => {
|
|
22
|
+
if (keyboardAvoiding) {
|
|
23
|
+
return (
|
|
24
|
+
<AtomicKeyboardAvoidingView style={style}>
|
|
25
|
+
{children}
|
|
26
|
+
</AtomicKeyboardAvoidingView>
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return <View style={style}>{children}</View>;
|
|
31
|
+
};
|