@umituz/react-native-design-system 1.5.33 → 1.5.34
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 +4 -1
- package/src/presentation/atoms/AtomicDatePicker.tsx +2 -1
- package/src/presentation/atoms/AtomicInput.tsx +19 -12
- package/src/index.js +0 -100
- package/src/presentation/atoms/AtomicAvatar.js +0 -84
- package/src/presentation/atoms/AtomicAvatarGroup.js +0 -82
- package/src/presentation/atoms/AtomicBadge.js +0 -167
- package/src/presentation/atoms/AtomicButton.js +0 -171
- package/src/presentation/atoms/AtomicCard.js +0 -69
- package/src/presentation/atoms/AtomicChip.js +0 -130
- package/src/presentation/atoms/AtomicDatePicker.js +0 -245
- package/src/presentation/atoms/AtomicDivider.js +0 -57
- package/src/presentation/atoms/AtomicFab.js +0 -67
- package/src/presentation/atoms/AtomicFilter.js +0 -103
- package/src/presentation/atoms/AtomicFormError.js +0 -63
- package/src/presentation/atoms/AtomicIcon.js +0 -29
- package/src/presentation/atoms/AtomicImage.js +0 -91
- package/src/presentation/atoms/AtomicInput.js +0 -201
- package/src/presentation/atoms/AtomicNumberInput.js +0 -124
- package/src/presentation/atoms/AtomicPicker.js +0 -298
- package/src/presentation/atoms/AtomicProgress.js +0 -79
- package/src/presentation/atoms/AtomicSearchBar.js +0 -45
- package/src/presentation/atoms/AtomicSort.js +0 -76
- package/src/presentation/atoms/AtomicSwitch.js +0 -103
- package/src/presentation/atoms/AtomicText.js +0 -22
- package/src/presentation/atoms/AtomicTextArea.js +0 -195
- package/src/presentation/atoms/AtomicTouchable.js +0 -137
- package/src/presentation/atoms/fab/styles/fabStyles.js +0 -62
- package/src/presentation/atoms/fab/types/index.js +0 -1
- package/src/presentation/atoms/filter/styles/filterStyles.js +0 -28
- package/src/presentation/atoms/filter/types/index.js +0 -1
- package/src/presentation/atoms/index.js +0 -145
- package/src/presentation/atoms/input/hooks/useInputState.js +0 -12
- package/src/presentation/atoms/input/styles/inputStyles.js +0 -58
- package/src/presentation/atoms/input/types/index.js +0 -1
- package/src/presentation/atoms/picker/styles/pickerStyles.js +0 -176
- package/src/presentation/atoms/picker/types/index.js +0 -1
- package/src/presentation/atoms/touchable/styles/touchableStyles.js +0 -53
- package/src/presentation/atoms/touchable/types/index.js +0 -1
- package/src/presentation/hooks/useResponsive.js +0 -81
- package/src/presentation/molecules/AtomicConfirmationModal.js +0 -153
- package/src/presentation/molecules/EmptyState.js +0 -67
- package/src/presentation/molecules/FormField.js +0 -75
- package/src/presentation/molecules/GridContainer.js +0 -76
- package/src/presentation/molecules/IconContainer.js +0 -59
- package/src/presentation/molecules/ListItem.js +0 -23
- package/src/presentation/molecules/ScreenHeader.js +0 -93
- package/src/presentation/molecules/SearchBar.js +0 -46
- package/src/presentation/molecules/SectionCard.js +0 -46
- package/src/presentation/molecules/SectionContainer.js +0 -63
- package/src/presentation/molecules/SectionHeader.js +0 -72
- package/src/presentation/molecules/confirmation-modal/styles/confirmationModalStyles.js +0 -114
- package/src/presentation/molecules/confirmation-modal/types/index.js +0 -6
- package/src/presentation/molecules/index.js +0 -16
- package/src/presentation/molecules/listitem/styles/listItemStyles.js +0 -14
- package/src/presentation/molecules/listitem/types/index.js +0 -1
- package/src/presentation/organisms/AppHeader.js +0 -77
- package/src/presentation/organisms/FormContainer.js +0 -126
- package/src/presentation/organisms/ScreenLayout.js +0 -68
- package/src/presentation/organisms/index.js +0 -13
- package/src/presentation/tokens/commonStyles.js +0 -219
- package/src/presentation/utils/platformConstants.js +0 -113
- package/src/presentation/utils/responsive.js +0 -451
- package/src/presentation/utils/variants/compound.js +0 -15
- package/src/presentation/utils/variants/core.js +0 -22
- package/src/presentation/utils/variants/helpers.js +0 -9
- package/src/presentation/utils/variants.js +0 -3
|
@@ -1,171 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { StyleSheet, TouchableOpacity, View } from 'react-native';
|
|
3
|
-
import { AtomicText } from './AtomicText';
|
|
4
|
-
import { Icon } from '@umituz/react-native-icon';
|
|
5
|
-
import { useAppDesignTokens } from '@umituz/react-native-theme';
|
|
6
|
-
export const AtomicButton = ({ title, children, onPress, variant = 'primary', size = 'md', disabled = false, icon, fullWidth = false, style, textStyle, testID, }) => {
|
|
7
|
-
const tokens = useAppDesignTokens();
|
|
8
|
-
const handlePress = () => {
|
|
9
|
-
if (!disabled) {
|
|
10
|
-
onPress();
|
|
11
|
-
}
|
|
12
|
-
};
|
|
13
|
-
// Size configurations
|
|
14
|
-
const sizeConfig = {
|
|
15
|
-
sm: {
|
|
16
|
-
paddingVertical: tokens.spacing.xs,
|
|
17
|
-
paddingHorizontal: tokens.spacing.sm,
|
|
18
|
-
fontSize: tokens.typography.bodySmall.fontSize,
|
|
19
|
-
iconSize: 16,
|
|
20
|
-
minHeight: 32,
|
|
21
|
-
},
|
|
22
|
-
md: {
|
|
23
|
-
paddingVertical: tokens.spacing.sm,
|
|
24
|
-
paddingHorizontal: tokens.spacing.md,
|
|
25
|
-
fontSize: tokens.typography.bodyMedium.fontSize,
|
|
26
|
-
iconSize: 20,
|
|
27
|
-
minHeight: 44,
|
|
28
|
-
},
|
|
29
|
-
lg: {
|
|
30
|
-
paddingVertical: tokens.spacing.md,
|
|
31
|
-
paddingHorizontal: tokens.spacing.lg,
|
|
32
|
-
fontSize: tokens.typography.bodyLarge.fontSize,
|
|
33
|
-
iconSize: 24,
|
|
34
|
-
minHeight: 52,
|
|
35
|
-
},
|
|
36
|
-
};
|
|
37
|
-
const config = sizeConfig[size];
|
|
38
|
-
// Variant styles
|
|
39
|
-
const getVariantStyles = () => {
|
|
40
|
-
const baseStyle = {
|
|
41
|
-
backgroundColor: tokens.colors.primary,
|
|
42
|
-
borderWidth: 0,
|
|
43
|
-
};
|
|
44
|
-
const baseTextStyle = {
|
|
45
|
-
color: tokens.colors.textInverse,
|
|
46
|
-
};
|
|
47
|
-
switch (variant) {
|
|
48
|
-
case 'primary':
|
|
49
|
-
return {
|
|
50
|
-
container: {
|
|
51
|
-
...baseStyle,
|
|
52
|
-
backgroundColor: tokens.colors.primary,
|
|
53
|
-
},
|
|
54
|
-
text: {
|
|
55
|
-
...baseTextStyle,
|
|
56
|
-
color: tokens.colors.textInverse,
|
|
57
|
-
},
|
|
58
|
-
};
|
|
59
|
-
case 'secondary':
|
|
60
|
-
return {
|
|
61
|
-
container: {
|
|
62
|
-
...baseStyle,
|
|
63
|
-
backgroundColor: tokens.colors.surfaceSecondary,
|
|
64
|
-
},
|
|
65
|
-
text: {
|
|
66
|
-
...baseTextStyle,
|
|
67
|
-
color: tokens.colors.textPrimary,
|
|
68
|
-
},
|
|
69
|
-
};
|
|
70
|
-
case 'outline':
|
|
71
|
-
return {
|
|
72
|
-
container: {
|
|
73
|
-
...baseStyle,
|
|
74
|
-
backgroundColor: 'transparent',
|
|
75
|
-
borderWidth: 1,
|
|
76
|
-
borderColor: tokens.colors.border,
|
|
77
|
-
},
|
|
78
|
-
text: {
|
|
79
|
-
...baseTextStyle,
|
|
80
|
-
color: tokens.colors.textPrimary,
|
|
81
|
-
},
|
|
82
|
-
};
|
|
83
|
-
case 'text':
|
|
84
|
-
return {
|
|
85
|
-
container: {
|
|
86
|
-
...baseStyle,
|
|
87
|
-
backgroundColor: 'transparent',
|
|
88
|
-
},
|
|
89
|
-
text: {
|
|
90
|
-
...baseTextStyle,
|
|
91
|
-
color: tokens.colors.primary,
|
|
92
|
-
},
|
|
93
|
-
};
|
|
94
|
-
case 'danger':
|
|
95
|
-
return {
|
|
96
|
-
container: {
|
|
97
|
-
...baseStyle,
|
|
98
|
-
backgroundColor: tokens.colors.error,
|
|
99
|
-
},
|
|
100
|
-
text: {
|
|
101
|
-
...baseTextStyle,
|
|
102
|
-
color: tokens.colors.textInverse,
|
|
103
|
-
},
|
|
104
|
-
};
|
|
105
|
-
default:
|
|
106
|
-
return {
|
|
107
|
-
container: baseStyle,
|
|
108
|
-
text: baseTextStyle,
|
|
109
|
-
};
|
|
110
|
-
}
|
|
111
|
-
};
|
|
112
|
-
const variantStyles = getVariantStyles();
|
|
113
|
-
const containerStyle = [
|
|
114
|
-
styles.button,
|
|
115
|
-
{
|
|
116
|
-
paddingVertical: config.paddingVertical,
|
|
117
|
-
paddingHorizontal: config.paddingHorizontal,
|
|
118
|
-
minHeight: config.minHeight,
|
|
119
|
-
borderRadius: tokens.borders.radius.md,
|
|
120
|
-
},
|
|
121
|
-
variantStyles.container,
|
|
122
|
-
fullWidth ? styles.fullWidth : undefined,
|
|
123
|
-
disabled ? styles.disabled : undefined,
|
|
124
|
-
style,
|
|
125
|
-
];
|
|
126
|
-
const buttonTextStyle = [
|
|
127
|
-
{
|
|
128
|
-
fontSize: config.fontSize,
|
|
129
|
-
fontWeight: '600',
|
|
130
|
-
},
|
|
131
|
-
variantStyles.text,
|
|
132
|
-
disabled ? styles.disabledText : undefined,
|
|
133
|
-
textStyle,
|
|
134
|
-
];
|
|
135
|
-
const buttonText = title || children;
|
|
136
|
-
const showIcon = icon;
|
|
137
|
-
const iconColor = variantStyles.text.color;
|
|
138
|
-
return (<TouchableOpacity style={containerStyle} onPress={handlePress} activeOpacity={0.8} disabled={disabled} testID={testID}>
|
|
139
|
-
<View style={styles.content}>
|
|
140
|
-
{showIcon ? (<Icon name={icon} customSize={config.iconSize} customColor={typeof iconColor === 'string' ? iconColor : undefined} style={styles.icon}/>) : null}
|
|
141
|
-
|
|
142
|
-
<AtomicText style={buttonTextStyle}>
|
|
143
|
-
{buttonText}
|
|
144
|
-
</AtomicText>
|
|
145
|
-
</View>
|
|
146
|
-
</TouchableOpacity>);
|
|
147
|
-
};
|
|
148
|
-
const styles = StyleSheet.create({
|
|
149
|
-
button: {
|
|
150
|
-
alignItems: 'center',
|
|
151
|
-
justifyContent: 'center',
|
|
152
|
-
flexDirection: 'row',
|
|
153
|
-
},
|
|
154
|
-
content: {
|
|
155
|
-
flexDirection: 'row',
|
|
156
|
-
alignItems: 'center',
|
|
157
|
-
justifyContent: 'center',
|
|
158
|
-
},
|
|
159
|
-
fullWidth: {
|
|
160
|
-
width: '100%',
|
|
161
|
-
},
|
|
162
|
-
disabled: {
|
|
163
|
-
opacity: 0.5,
|
|
164
|
-
},
|
|
165
|
-
disabledText: {
|
|
166
|
-
opacity: 0.7,
|
|
167
|
-
},
|
|
168
|
-
icon: {
|
|
169
|
-
marginRight: 8,
|
|
170
|
-
},
|
|
171
|
-
});
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { View, Pressable } from 'react-native';
|
|
3
|
-
import { useAppDesignTokens } from '@umituz/react-native-theme';
|
|
4
|
-
export const AtomicCard = ({ variant = 'elevated', padding = 'md', onPress, disabled = false, style, children, testID, }) => {
|
|
5
|
-
const tokens = useAppDesignTokens();
|
|
6
|
-
const handlePress = () => {
|
|
7
|
-
if (onPress && !disabled) {
|
|
8
|
-
onPress();
|
|
9
|
-
}
|
|
10
|
-
};
|
|
11
|
-
// Map padding to token values
|
|
12
|
-
const getPaddingValue = () => {
|
|
13
|
-
const paddingMap = {
|
|
14
|
-
none: 0,
|
|
15
|
-
sm: tokens.spacing.sm,
|
|
16
|
-
md: tokens.spacing.md,
|
|
17
|
-
lg: tokens.spacing.lg,
|
|
18
|
-
xl: tokens.spacing.xl,
|
|
19
|
-
};
|
|
20
|
-
return paddingMap[padding];
|
|
21
|
-
};
|
|
22
|
-
// Get variant styles
|
|
23
|
-
const getVariantStyle = () => {
|
|
24
|
-
const baseStyle = {
|
|
25
|
-
backgroundColor: tokens.colors.surface,
|
|
26
|
-
borderRadius: tokens.borders.radius.md,
|
|
27
|
-
};
|
|
28
|
-
switch (variant) {
|
|
29
|
-
case 'elevated':
|
|
30
|
-
return {
|
|
31
|
-
...baseStyle,
|
|
32
|
-
borderWidth: 1,
|
|
33
|
-
borderColor: tokens.colors.border,
|
|
34
|
-
};
|
|
35
|
-
case 'outlined':
|
|
36
|
-
return {
|
|
37
|
-
...baseStyle,
|
|
38
|
-
borderWidth: 1,
|
|
39
|
-
borderColor: tokens.colors.border,
|
|
40
|
-
};
|
|
41
|
-
case 'flat':
|
|
42
|
-
return {
|
|
43
|
-
...baseStyle,
|
|
44
|
-
borderWidth: 0,
|
|
45
|
-
};
|
|
46
|
-
default:
|
|
47
|
-
return baseStyle;
|
|
48
|
-
}
|
|
49
|
-
};
|
|
50
|
-
const cardStyle = [
|
|
51
|
-
getVariantStyle(),
|
|
52
|
-
{
|
|
53
|
-
padding: getPaddingValue(),
|
|
54
|
-
opacity: disabled ? 0.5 : 1,
|
|
55
|
-
},
|
|
56
|
-
style,
|
|
57
|
-
];
|
|
58
|
-
const cardContent = (<View style={cardStyle} testID={testID}>
|
|
59
|
-
{children}
|
|
60
|
-
</View>);
|
|
61
|
-
// If onPress provided, wrap with pressable
|
|
62
|
-
if (onPress && !disabled) {
|
|
63
|
-
return (<Pressable onPress={handlePress}>
|
|
64
|
-
{cardContent}
|
|
65
|
-
</Pressable>);
|
|
66
|
-
}
|
|
67
|
-
// Otherwise just return static card
|
|
68
|
-
return cardContent;
|
|
69
|
-
};
|
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* AtomicChip - Universal Chip/Tag Component
|
|
3
|
-
*
|
|
4
|
-
* Displays small tags, labels, or status indicators
|
|
5
|
-
* Theme: {{THEME_NAME}} ({{CATEGORY}} category)
|
|
6
|
-
*
|
|
7
|
-
* Atomic Design Level: ATOM
|
|
8
|
-
* Purpose: Tag and label display
|
|
9
|
-
*
|
|
10
|
-
* Usage:
|
|
11
|
-
* - Category tags
|
|
12
|
-
* - Status indicators
|
|
13
|
-
* - Filter chips
|
|
14
|
-
* - Skill labels
|
|
15
|
-
* - Badge displays
|
|
16
|
-
*/
|
|
17
|
-
import React from 'react';
|
|
18
|
-
import { View, TouchableOpacity } from 'react-native';
|
|
19
|
-
import { AtomicText } from './AtomicText';
|
|
20
|
-
import { AtomicIcon } from './AtomicIcon';
|
|
21
|
-
import { useAppDesignTokens } from '@umituz/react-native-theme';
|
|
22
|
-
// =============================================================================
|
|
23
|
-
// COMPONENT IMPLEMENTATION
|
|
24
|
-
// =============================================================================
|
|
25
|
-
export const AtomicChip = ({ children, variant = 'filled', size = 'md', color = 'primary', backgroundColor, textColor, borderColor, leadingIcon, trailingIcon, clickable = false, onPress, selected = false, disabled = false, style, testID, }) => {
|
|
26
|
-
const tokens = useAppDesignTokens();
|
|
27
|
-
// Size mapping
|
|
28
|
-
const sizeMap = {
|
|
29
|
-
sm: {
|
|
30
|
-
paddingHorizontal: tokens.spacing.sm,
|
|
31
|
-
paddingVertical: tokens.spacing.xs,
|
|
32
|
-
fontSize: tokens.typography.bodySmall.fontSize,
|
|
33
|
-
iconSize: 'xs'
|
|
34
|
-
},
|
|
35
|
-
md: {
|
|
36
|
-
paddingHorizontal: tokens.spacing.md,
|
|
37
|
-
paddingVertical: tokens.spacing.sm,
|
|
38
|
-
fontSize: tokens.typography.bodyMedium.fontSize,
|
|
39
|
-
iconSize: 'sm'
|
|
40
|
-
},
|
|
41
|
-
lg: {
|
|
42
|
-
paddingHorizontal: tokens.spacing.md,
|
|
43
|
-
paddingVertical: tokens.spacing.sm,
|
|
44
|
-
fontSize: tokens.typography.bodyLarge.fontSize,
|
|
45
|
-
iconSize: 'sm'
|
|
46
|
-
},
|
|
47
|
-
};
|
|
48
|
-
const sizeConfig = sizeMap[size];
|
|
49
|
-
// Color mapping
|
|
50
|
-
const colorMap = {
|
|
51
|
-
primary: {
|
|
52
|
-
filled: { bg: tokens.colors.primary, text: tokens.colors.onPrimary, border: tokens.colors.primary },
|
|
53
|
-
outlined: { bg: 'transparent', text: tokens.colors.primary, border: tokens.colors.primary },
|
|
54
|
-
soft: { bg: tokens.colors.primaryContainer, text: tokens.colors.onPrimaryContainer, border: 'transparent' },
|
|
55
|
-
},
|
|
56
|
-
secondary: {
|
|
57
|
-
filled: { bg: tokens.colors.secondary, text: tokens.colors.onSecondary, border: tokens.colors.secondary },
|
|
58
|
-
outlined: { bg: 'transparent', text: tokens.colors.secondary, border: tokens.colors.secondary },
|
|
59
|
-
soft: { bg: tokens.colors.secondaryContainer, text: tokens.colors.onSecondaryContainer, border: 'transparent' },
|
|
60
|
-
},
|
|
61
|
-
success: {
|
|
62
|
-
filled: { bg: tokens.colors.success, text: tokens.colors.onSuccess, border: tokens.colors.success },
|
|
63
|
-
outlined: { bg: 'transparent', text: tokens.colors.success, border: tokens.colors.success },
|
|
64
|
-
soft: { bg: tokens.colors.successContainer, text: tokens.colors.onSuccessContainer, border: 'transparent' },
|
|
65
|
-
},
|
|
66
|
-
warning: {
|
|
67
|
-
filled: { bg: tokens.colors.warning, text: tokens.colors.onWarning, border: tokens.colors.warning },
|
|
68
|
-
outlined: { bg: 'transparent', text: tokens.colors.warning, border: tokens.colors.warning },
|
|
69
|
-
soft: { bg: tokens.colors.warningContainer, text: tokens.colors.onWarningContainer, border: 'transparent' },
|
|
70
|
-
},
|
|
71
|
-
error: {
|
|
72
|
-
filled: { bg: tokens.colors.error, text: tokens.colors.onError, border: tokens.colors.error },
|
|
73
|
-
outlined: { bg: 'transparent', text: tokens.colors.error, border: tokens.colors.error },
|
|
74
|
-
soft: { bg: tokens.colors.errorContainer, text: tokens.colors.onErrorContainer, border: 'transparent' },
|
|
75
|
-
},
|
|
76
|
-
info: {
|
|
77
|
-
filled: { bg: tokens.colors.info, text: tokens.colors.onInfo, border: tokens.colors.info },
|
|
78
|
-
outlined: { bg: 'transparent', text: tokens.colors.info, border: tokens.colors.info },
|
|
79
|
-
soft: { bg: tokens.colors.infoContainer, text: tokens.colors.onInfoContainer, border: 'transparent' },
|
|
80
|
-
},
|
|
81
|
-
};
|
|
82
|
-
const colorConfig = colorMap[color][variant];
|
|
83
|
-
// Apply custom colors if provided
|
|
84
|
-
const finalBackgroundColor = backgroundColor || colorConfig.bg;
|
|
85
|
-
const finalTextColor = textColor || colorConfig.text;
|
|
86
|
-
const finalBorderColor = borderColor || colorConfig.border;
|
|
87
|
-
// Handle disabled state
|
|
88
|
-
const isDisabled = disabled || (!clickable && !onPress);
|
|
89
|
-
const opacity = isDisabled ? 0.5 : 1;
|
|
90
|
-
// Handle selected state
|
|
91
|
-
const selectedStyle = selected ? {
|
|
92
|
-
borderWidth: tokens.borders.width.medium,
|
|
93
|
-
borderColor: tokens.colors.primary,
|
|
94
|
-
} : {};
|
|
95
|
-
const chipStyle = {
|
|
96
|
-
flexDirection: 'row',
|
|
97
|
-
alignItems: 'center',
|
|
98
|
-
justifyContent: 'center',
|
|
99
|
-
paddingHorizontal: sizeConfig.paddingHorizontal,
|
|
100
|
-
paddingVertical: sizeConfig.paddingVertical,
|
|
101
|
-
backgroundColor: finalBackgroundColor,
|
|
102
|
-
borderRadius: tokens.borders.radius.xl,
|
|
103
|
-
borderWidth: variant === 'outlined' ? 1 : 0,
|
|
104
|
-
borderColor: finalBorderColor,
|
|
105
|
-
opacity,
|
|
106
|
-
...selectedStyle,
|
|
107
|
-
};
|
|
108
|
-
const textStyle = {
|
|
109
|
-
fontSize: sizeConfig.fontSize,
|
|
110
|
-
fontWeight: tokens.typography.medium,
|
|
111
|
-
};
|
|
112
|
-
const iconColor = finalTextColor;
|
|
113
|
-
const content = (<View style={[chipStyle, style]} testID={testID}>
|
|
114
|
-
{leadingIcon && (<AtomicIcon name={leadingIcon} size={sizeConfig.iconSize} customColor={iconColor} style={{ marginRight: tokens.spacing.xs }}/>)}
|
|
115
|
-
<AtomicText type="labelMedium" color={finalTextColor} style={textStyle}>
|
|
116
|
-
{children}
|
|
117
|
-
</AtomicText>
|
|
118
|
-
{trailingIcon && (<AtomicIcon name={trailingIcon} size={sizeConfig.iconSize} customColor={iconColor} style={{ marginLeft: tokens.spacing.xs }}/>)}
|
|
119
|
-
</View>);
|
|
120
|
-
if (clickable && onPress && !disabled) {
|
|
121
|
-
return (<TouchableOpacity onPress={onPress} activeOpacity={0.7}>
|
|
122
|
-
{content}
|
|
123
|
-
</TouchableOpacity>);
|
|
124
|
-
}
|
|
125
|
-
return content;
|
|
126
|
-
};
|
|
127
|
-
// =============================================================================
|
|
128
|
-
// EXPORTS
|
|
129
|
-
// =============================================================================
|
|
130
|
-
export default AtomicChip;
|
|
@@ -1,245 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* AtomicDatePicker Component
|
|
3
|
-
*
|
|
4
|
-
* A reusable date picker component that wraps the native date picker
|
|
5
|
-
* with consistent styling and behavior across platforms.
|
|
6
|
-
*
|
|
7
|
-
* Features:
|
|
8
|
-
* - Platform-specific native pickers (iOS wheel, Android dialog)
|
|
9
|
-
* - Consistent styling with design tokens
|
|
10
|
-
* - Locale-aware date/time formatting (native Date methods)
|
|
11
|
-
* - Timezone-aware (respects device timezone)
|
|
12
|
-
* - Automatic language integration (native locale support)
|
|
13
|
-
* - Optional label and error states
|
|
14
|
-
* - Minimum and maximum date constraints
|
|
15
|
-
* - Disabled state support
|
|
16
|
-
* - Theme-aware styling
|
|
17
|
-
* - Proper keyboard avoidance on iOS
|
|
18
|
-
*
|
|
19
|
-
* Usage:
|
|
20
|
-
* ```tsx
|
|
21
|
-
* const [selectedDate, setSelectedDate] = useState(new Date());
|
|
22
|
-
*
|
|
23
|
-
* <AtomicDatePicker
|
|
24
|
-
* value={selectedDate}
|
|
25
|
-
* onChange={setSelectedDate}
|
|
26
|
-
* label="Birth Date"
|
|
27
|
-
* minimumDate={new Date(1900, 0, 1)}
|
|
28
|
-
* maximumDate={new Date()}
|
|
29
|
-
* />
|
|
30
|
-
* ```
|
|
31
|
-
*
|
|
32
|
-
* Platform Behavior:
|
|
33
|
-
* - iOS: Opens modal with spinner wheel, requires "Done" button
|
|
34
|
-
* - Android: Opens native dialog, auto-closes on selection
|
|
35
|
-
*
|
|
36
|
-
* @module AtomicDatePicker
|
|
37
|
-
*/
|
|
38
|
-
import React, { useState } from 'react';
|
|
39
|
-
import { View, Text, TouchableOpacity, StyleSheet, Modal, useWindowDimensions, } from 'react-native';
|
|
40
|
-
import DateTimePicker from '@react-native-community/datetimepicker';
|
|
41
|
-
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
42
|
-
import { useAppDesignTokens } from '@umituz/react-native-theme';
|
|
43
|
-
import { useResponsive } from '../hooks/useResponsive';
|
|
44
|
-
import { AtomicIcon } from './AtomicIcon';
|
|
45
|
-
/**
|
|
46
|
-
* AtomicDatePicker - Universal date/time picker component
|
|
47
|
-
*
|
|
48
|
-
* Wraps @react-native-community/datetimepicker with:
|
|
49
|
-
* - Theme integration
|
|
50
|
-
* - Platform-specific modal handling
|
|
51
|
-
* - Error states
|
|
52
|
-
* - Disabled states
|
|
53
|
-
* - Responsive sizing
|
|
54
|
-
*/
|
|
55
|
-
export const AtomicDatePicker = ({ value, onChange, label, error, disabled = false, minimumDate, maximumDate, mode = 'date', placeholder = 'Select date', testID, }) => {
|
|
56
|
-
const tokens = useAppDesignTokens();
|
|
57
|
-
const { height } = useWindowDimensions();
|
|
58
|
-
const insets = useSafeAreaInsets();
|
|
59
|
-
const { isTabletDevice } = useResponsive();
|
|
60
|
-
const [show, setShow] = useState(false);
|
|
61
|
-
/**
|
|
62
|
-
* Handle date/time change
|
|
63
|
-
* Universal handler that works across all platforms
|
|
64
|
-
* Note: event.type can be 'set', 'dismissed', or 'neutralButtonPressed'
|
|
65
|
-
*/
|
|
66
|
-
const handleChange = (event, selectedDate) => {
|
|
67
|
-
// Close picker when user confirms or dismisses
|
|
68
|
-
// iOS: Stays open until "Done" button (handled separately)
|
|
69
|
-
// Android/Web: Auto-closes on selection
|
|
70
|
-
if (event.type === 'set' || event.type === 'dismissed') {
|
|
71
|
-
setShow(false);
|
|
72
|
-
}
|
|
73
|
-
// Update value only if date was selected (not dismissed)
|
|
74
|
-
if (event.type === 'set' && selectedDate) {
|
|
75
|
-
onChange(selectedDate);
|
|
76
|
-
}
|
|
77
|
-
};
|
|
78
|
-
/**
|
|
79
|
-
* Format date based on mode
|
|
80
|
-
* Uses native Date formatting (locale-aware)
|
|
81
|
-
*/
|
|
82
|
-
const formatDate = (date) => {
|
|
83
|
-
if (mode === 'time') {
|
|
84
|
-
// Format time only
|
|
85
|
-
return date.toLocaleTimeString([], {
|
|
86
|
-
hour: '2-digit',
|
|
87
|
-
minute: '2-digit'
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
if (mode === 'datetime') {
|
|
91
|
-
// Format date + time
|
|
92
|
-
const dateStr = date.toLocaleDateString([], {
|
|
93
|
-
year: 'numeric',
|
|
94
|
-
month: 'short',
|
|
95
|
-
day: 'numeric',
|
|
96
|
-
});
|
|
97
|
-
const timeStr = date.toLocaleTimeString([], {
|
|
98
|
-
hour: '2-digit',
|
|
99
|
-
minute: '2-digit'
|
|
100
|
-
});
|
|
101
|
-
return `${dateStr} ${timeStr}`;
|
|
102
|
-
}
|
|
103
|
-
// Format date only
|
|
104
|
-
return date.toLocaleDateString([], {
|
|
105
|
-
year: 'numeric',
|
|
106
|
-
month: 'long',
|
|
107
|
-
day: 'numeric',
|
|
108
|
-
});
|
|
109
|
-
};
|
|
110
|
-
/**
|
|
111
|
-
* Determine icon color based on state
|
|
112
|
-
*/
|
|
113
|
-
const getIconColor = () => {
|
|
114
|
-
if (disabled)
|
|
115
|
-
return 'secondary';
|
|
116
|
-
if (error)
|
|
117
|
-
return 'error';
|
|
118
|
-
return 'primary';
|
|
119
|
-
};
|
|
120
|
-
const styles = getStyles(tokens, height, insets);
|
|
121
|
-
return (<View style={styles.container} testID={testID}>
|
|
122
|
-
{label && (<Text style={styles.label} testID={testID ? `${testID}-label` : undefined}>
|
|
123
|
-
{label}
|
|
124
|
-
</Text>)}
|
|
125
|
-
|
|
126
|
-
<TouchableOpacity style={[
|
|
127
|
-
styles.button,
|
|
128
|
-
error ? styles.buttonError : undefined,
|
|
129
|
-
disabled ? styles.buttonDisabled : undefined,
|
|
130
|
-
]} onPress={() => !disabled && setShow(true)} disabled={disabled} testID={testID ? `${testID}-button` : undefined} accessibilityLabel={label || placeholder} accessibilityRole="button" accessibilityState={{ disabled }}>
|
|
131
|
-
<AtomicIcon name="calendar" color={getIconColor()} size="md"/>
|
|
132
|
-
<Text style={[
|
|
133
|
-
styles.text,
|
|
134
|
-
disabled ? styles.textDisabled : undefined,
|
|
135
|
-
error ? styles.textError : undefined,
|
|
136
|
-
]}>
|
|
137
|
-
{value ? formatDate(value) : placeholder}
|
|
138
|
-
</Text>
|
|
139
|
-
</TouchableOpacity>
|
|
140
|
-
|
|
141
|
-
{error && (<Text style={styles.errorText} testID={testID ? `${testID}-error` : undefined}>
|
|
142
|
-
{error}
|
|
143
|
-
</Text>)}
|
|
144
|
-
|
|
145
|
-
{/* Universal DatePicker - Works across iOS, Android, Web */}
|
|
146
|
-
{show && (<Modal transparent animationType={isTabletDevice ? 'fade' : 'slide'} visible={show} onRequestClose={() => setShow(false)}>
|
|
147
|
-
<TouchableOpacity style={styles.modalOverlay} activeOpacity={1} onPress={() => setShow(false)} accessibilityLabel="Close date picker" accessibilityRole="button">
|
|
148
|
-
<View style={styles.pickerContainer} onStartShouldSetResponder={() => true}>
|
|
149
|
-
<DateTimePicker value={value || new Date()} mode={mode} display="spinner" onChange={handleChange} minimumDate={minimumDate} maximumDate={maximumDate} testID={testID ? `${testID}-picker` : undefined}/>
|
|
150
|
-
<View style={styles.buttonContainer}>
|
|
151
|
-
<TouchableOpacity style={styles.doneButton} onPress={() => setShow(false)} testID={testID ? `${testID}-done` : undefined} accessibilityLabel="Done" accessibilityRole="button">
|
|
152
|
-
<Text style={styles.doneText}>Done</Text>
|
|
153
|
-
</TouchableOpacity>
|
|
154
|
-
</View>
|
|
155
|
-
</View>
|
|
156
|
-
</TouchableOpacity>
|
|
157
|
-
</Modal>)}
|
|
158
|
-
</View>);
|
|
159
|
-
};
|
|
160
|
-
/**
|
|
161
|
-
* Get component styles based on design tokens
|
|
162
|
-
*/
|
|
163
|
-
const getStyles = (tokens, height, insets) => {
|
|
164
|
-
// Responsive button sizing based on device height
|
|
165
|
-
const buttonMinWidth = height <= 667 ? Math.min(height * 0.25, 150) : 200;
|
|
166
|
-
return StyleSheet.create({
|
|
167
|
-
container: {
|
|
168
|
-
marginBottom: tokens.spacing.md,
|
|
169
|
-
},
|
|
170
|
-
label: {
|
|
171
|
-
fontSize: tokens.typography.bodyMedium.fontSize,
|
|
172
|
-
fontWeight: tokens.typography.semibold,
|
|
173
|
-
color: tokens.colors.textPrimary,
|
|
174
|
-
marginBottom: tokens.spacing.sm,
|
|
175
|
-
},
|
|
176
|
-
button: {
|
|
177
|
-
flexDirection: 'row',
|
|
178
|
-
alignItems: 'center',
|
|
179
|
-
backgroundColor: tokens.colors.surface,
|
|
180
|
-
borderWidth: 1,
|
|
181
|
-
borderColor: tokens.colors.border,
|
|
182
|
-
borderRadius: tokens.borders.radius.lg,
|
|
183
|
-
paddingHorizontal: tokens.spacing.md,
|
|
184
|
-
paddingVertical: tokens.spacing.md,
|
|
185
|
-
gap: tokens.spacing.sm,
|
|
186
|
-
minHeight: 48, // Apple HIG minimum touch target
|
|
187
|
-
},
|
|
188
|
-
buttonError: {
|
|
189
|
-
borderColor: tokens.colors.error,
|
|
190
|
-
borderWidth: tokens.borders.width.medium,
|
|
191
|
-
},
|
|
192
|
-
buttonDisabled: {
|
|
193
|
-
backgroundColor: tokens.colors.surfaceDisabled,
|
|
194
|
-
opacity: tokens.opacity.disabled,
|
|
195
|
-
},
|
|
196
|
-
text: {
|
|
197
|
-
flex: 1,
|
|
198
|
-
fontSize: tokens.typography.bodyLarge.fontSize,
|
|
199
|
-
color: tokens.colors.textPrimary,
|
|
200
|
-
},
|
|
201
|
-
textDisabled: {
|
|
202
|
-
color: tokens.colors.textDisabled,
|
|
203
|
-
},
|
|
204
|
-
textError: {
|
|
205
|
-
color: tokens.colors.error,
|
|
206
|
-
},
|
|
207
|
-
errorText: {
|
|
208
|
-
fontSize: tokens.typography.bodySmall.fontSize,
|
|
209
|
-
color: tokens.colors.error,
|
|
210
|
-
marginTop: tokens.spacing.xs,
|
|
211
|
-
marginLeft: tokens.spacing.xs,
|
|
212
|
-
},
|
|
213
|
-
modalOverlay: {
|
|
214
|
-
flex: 1,
|
|
215
|
-
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
|
216
|
-
justifyContent: 'flex-start',
|
|
217
|
-
},
|
|
218
|
-
pickerContainer: {
|
|
219
|
-
backgroundColor: tokens.colors.surface,
|
|
220
|
-
borderTopLeftRadius: tokens.borders.radius.xl,
|
|
221
|
-
borderTopRightRadius: tokens.borders.radius.xl,
|
|
222
|
-
paddingTop: tokens.spacing.lg,
|
|
223
|
-
paddingBottom: Math.max(insets.bottom + tokens.spacing.md, tokens.spacing.xl),
|
|
224
|
-
},
|
|
225
|
-
buttonContainer: {
|
|
226
|
-
alignItems: 'center',
|
|
227
|
-
marginTop: tokens.spacing.md,
|
|
228
|
-
paddingHorizontal: tokens.spacing.lg,
|
|
229
|
-
},
|
|
230
|
-
doneButton: {
|
|
231
|
-
backgroundColor: tokens.colors.primary,
|
|
232
|
-
paddingHorizontal: tokens.spacing.xl,
|
|
233
|
-
paddingVertical: tokens.spacing.sm,
|
|
234
|
-
borderRadius: tokens.borders.radius.lg,
|
|
235
|
-
minWidth: buttonMinWidth,
|
|
236
|
-
alignItems: 'center',
|
|
237
|
-
minHeight: 44, // Apple HIG minimum touch target
|
|
238
|
-
},
|
|
239
|
-
doneText: {
|
|
240
|
-
color: tokens.colors.onPrimary,
|
|
241
|
-
fontSize: tokens.typography.bodyLarge.fontSize,
|
|
242
|
-
fontWeight: tokens.typography.semibold,
|
|
243
|
-
},
|
|
244
|
-
});
|
|
245
|
-
};
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* AtomicDivider - Universal Divider Component
|
|
3
|
-
*
|
|
4
|
-
* Displays horizontal or vertical dividers for content separation
|
|
5
|
-
* Theme: {{THEME_NAME}} ({{CATEGORY}} category)
|
|
6
|
-
*
|
|
7
|
-
* Atomic Design Level: ATOM
|
|
8
|
-
* Purpose: Content separation and visual hierarchy
|
|
9
|
-
*
|
|
10
|
-
* Usage:
|
|
11
|
-
* - Section separators
|
|
12
|
-
* - List item dividers
|
|
13
|
-
* - Card separators
|
|
14
|
-
* - Menu dividers
|
|
15
|
-
* - Form field separators
|
|
16
|
-
*/
|
|
17
|
-
import React from 'react';
|
|
18
|
-
import { View } from 'react-native';
|
|
19
|
-
import { useAppDesignTokens } from '@umituz/react-native-theme';
|
|
20
|
-
// =============================================================================
|
|
21
|
-
// COMPONENT IMPLEMENTATION
|
|
22
|
-
// =============================================================================
|
|
23
|
-
export const AtomicDivider = ({ orientation = 'horizontal', thickness = 'thin', color, length, margin, marginTop, marginBottom, marginLeft, marginRight, style, testID, }) => {
|
|
24
|
-
const tokens = useAppDesignTokens();
|
|
25
|
-
// Thickness mapping
|
|
26
|
-
const thicknessMap = {
|
|
27
|
-
thin: 1,
|
|
28
|
-
medium: 2,
|
|
29
|
-
thick: 4,
|
|
30
|
-
};
|
|
31
|
-
const dividerThickness = thicknessMap[thickness];
|
|
32
|
-
const dividerColor = color || tokens.colors.border;
|
|
33
|
-
// Compute final length values with proper type handling
|
|
34
|
-
const finalLength = length !== undefined ? length : (orientation === 'horizontal' ? '100%' : 20);
|
|
35
|
-
// Base styles for all dividers
|
|
36
|
-
const baseStyle = {
|
|
37
|
-
backgroundColor: dividerColor,
|
|
38
|
-
margin: margin,
|
|
39
|
-
marginTop: marginTop,
|
|
40
|
-
marginBottom: marginBottom,
|
|
41
|
-
marginLeft: marginLeft,
|
|
42
|
-
marginRight: marginRight,
|
|
43
|
-
};
|
|
44
|
-
// Orientation-specific styles with explicit type casting
|
|
45
|
-
const orientationStyle = (orientation === 'horizontal' ? {
|
|
46
|
-
width: finalLength,
|
|
47
|
-
height: dividerThickness,
|
|
48
|
-
} : {
|
|
49
|
-
width: dividerThickness,
|
|
50
|
-
height: finalLength,
|
|
51
|
-
});
|
|
52
|
-
return (<View style={[baseStyle, orientationStyle, style]} testID={testID}/>);
|
|
53
|
-
};
|
|
54
|
-
// =============================================================================
|
|
55
|
-
// EXPORTS
|
|
56
|
-
// =============================================================================
|
|
57
|
-
export default AtomicDivider;
|