@umituz/react-native-design-system 1.5.36 → 1.5.38
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/README.md +2 -2
- package/package.json +7 -5
- package/src/index.ts +29 -221
- package/src/presentation/organisms/AppHeader.tsx +3 -5
- package/src/presentation/tokens/commonStyles.ts +1 -1
- package/src/presentation/atoms/AtomicAvatar.tsx +0 -157
- package/src/presentation/atoms/AtomicAvatarGroup.tsx +0 -169
- package/src/presentation/atoms/AtomicBadge.tsx +0 -232
- package/src/presentation/atoms/AtomicButton.tsx +0 -236
- package/src/presentation/atoms/AtomicCard.tsx +0 -107
- package/src/presentation/atoms/AtomicChip.tsx +0 -223
- package/src/presentation/atoms/AtomicDatePicker.tsx +0 -347
- package/src/presentation/atoms/AtomicDivider.tsx +0 -114
- package/src/presentation/atoms/AtomicFab.tsx +0 -98
- package/src/presentation/atoms/AtomicFilter.tsx +0 -154
- package/src/presentation/atoms/AtomicFormError.tsx +0 -105
- package/src/presentation/atoms/AtomicIcon.tsx +0 -40
- package/src/presentation/atoms/AtomicImage.tsx +0 -149
- package/src/presentation/atoms/AtomicInput.tsx +0 -363
- package/src/presentation/atoms/AtomicNumberInput.tsx +0 -182
- package/src/presentation/atoms/AtomicPicker.tsx +0 -458
- package/src/presentation/atoms/AtomicProgress.tsx +0 -139
- package/src/presentation/atoms/AtomicSearchBar.tsx +0 -114
- package/src/presentation/atoms/AtomicSort.tsx +0 -145
- package/src/presentation/atoms/AtomicSwitch.tsx +0 -166
- package/src/presentation/atoms/AtomicText.tsx +0 -55
- package/src/presentation/atoms/AtomicTextArea.tsx +0 -313
- package/src/presentation/atoms/AtomicTouchable.tsx +0 -209
- package/src/presentation/atoms/fab/styles/fabStyles.ts +0 -69
- package/src/presentation/atoms/fab/types/index.ts +0 -82
- package/src/presentation/atoms/filter/styles/filterStyles.ts +0 -32
- package/src/presentation/atoms/filter/types/index.ts +0 -89
- package/src/presentation/atoms/index.ts +0 -366
- package/src/presentation/atoms/input/hooks/useInputState.ts +0 -15
- package/src/presentation/atoms/input/styles/inputStyles.ts +0 -66
- package/src/presentation/atoms/input/types/index.ts +0 -25
- package/src/presentation/atoms/picker/styles/pickerStyles.ts +0 -207
- package/src/presentation/atoms/picker/types/index.ts +0 -40
- package/src/presentation/atoms/touchable/styles/touchableStyles.ts +0 -62
- package/src/presentation/atoms/touchable/types/index.ts +0 -155
- package/src/presentation/hooks/useResponsive.ts +0 -180
- package/src/presentation/molecules/AtomicConfirmationModal.tsx +0 -243
- package/src/presentation/molecules/EmptyState.tsx +0 -130
- package/src/presentation/molecules/FormField.tsx +0 -128
- package/src/presentation/molecules/GridContainer.tsx +0 -124
- package/src/presentation/molecules/IconContainer.tsx +0 -94
- package/src/presentation/molecules/ListItem.tsx +0 -36
- package/src/presentation/molecules/ScreenHeader.tsx +0 -140
- package/src/presentation/molecules/SearchBar.tsx +0 -85
- package/src/presentation/molecules/SectionCard.tsx +0 -74
- package/src/presentation/molecules/SectionContainer.tsx +0 -106
- package/src/presentation/molecules/SectionHeader.tsx +0 -125
- package/src/presentation/molecules/confirmation-modal/styles/confirmationModalStyles.ts +0 -133
- package/src/presentation/molecules/confirmation-modal/types/index.ts +0 -105
- package/src/presentation/molecules/index.ts +0 -41
- package/src/presentation/molecules/listitem/styles/listItemStyles.ts +0 -19
- package/src/presentation/molecules/listitem/types/index.ts +0 -17
- package/src/presentation/organisms/FormContainer.tsx +0 -180
- package/src/presentation/organisms/ScreenLayout.tsx +0 -171
- package/src/presentation/organisms/index.ts +0 -25
- package/src/presentation/utils/platformConstants.ts +0 -124
- package/src/presentation/utils/responsive.ts +0 -516
|
@@ -1,169 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* AtomicAvatarGroup - Universal Avatar Group Component
|
|
3
|
-
*
|
|
4
|
-
* Displays multiple avatars in a group with overlap and overflow handling
|
|
5
|
-
* Theme: {{THEME_NAME}} ({{CATEGORY}} category)
|
|
6
|
-
*
|
|
7
|
-
* Atomic Design Level: ATOM
|
|
8
|
-
* Purpose: Multiple avatar display with group behavior
|
|
9
|
-
*
|
|
10
|
-
* Usage:
|
|
11
|
-
* - Team member avatars
|
|
12
|
-
* - Group chat participants
|
|
13
|
-
* - Project collaborators
|
|
14
|
-
* - Event attendees
|
|
15
|
-
* - Social connections
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
import React from 'react';
|
|
19
|
-
import { View, StyleSheet, ViewStyle } from 'react-native';
|
|
20
|
-
import { AtomicAvatar, AtomicAvatarProps } from './AtomicAvatar';
|
|
21
|
-
import { AtomicText } from './AtomicText';
|
|
22
|
-
import { useAppDesignTokens } from '@umituz/react-native-theme';
|
|
23
|
-
|
|
24
|
-
// =============================================================================
|
|
25
|
-
// TYPE DEFINITIONS
|
|
26
|
-
// =============================================================================
|
|
27
|
-
|
|
28
|
-
export interface AvatarData {
|
|
29
|
-
id: string;
|
|
30
|
-
source?: { uri: string } | number;
|
|
31
|
-
name?: string;
|
|
32
|
-
backgroundColor?: string;
|
|
33
|
-
textColor?: string;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export interface AtomicAvatarGroupProps {
|
|
37
|
-
/** Array of avatar data */
|
|
38
|
-
avatars: AvatarData[];
|
|
39
|
-
/** Maximum number of avatars to show */
|
|
40
|
-
maxVisible?: number;
|
|
41
|
-
/** Avatar size */
|
|
42
|
-
size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'xxl';
|
|
43
|
-
/** Custom avatar size */
|
|
44
|
-
customSize?: number;
|
|
45
|
-
/** Spacing between avatars */
|
|
46
|
-
spacing?: number;
|
|
47
|
-
/** Whether to show overflow count */
|
|
48
|
-
showOverflow?: boolean;
|
|
49
|
-
/** Overflow count background color */
|
|
50
|
-
overflowBackgroundColor?: string;
|
|
51
|
-
/** Overflow count text color */
|
|
52
|
-
overflowTextColor?: string;
|
|
53
|
-
/** Avatar border width */
|
|
54
|
-
borderWidth?: number;
|
|
55
|
-
/** Avatar border color */
|
|
56
|
-
borderColor?: string;
|
|
57
|
-
/** Style overrides */
|
|
58
|
-
style?: ViewStyle;
|
|
59
|
-
/** Test ID for testing */
|
|
60
|
-
testID?: string;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// =============================================================================
|
|
64
|
-
// COMPONENT IMPLEMENTATION
|
|
65
|
-
// =============================================================================
|
|
66
|
-
|
|
67
|
-
export const AtomicAvatarGroup: React.FC<AtomicAvatarGroupProps> = ({
|
|
68
|
-
avatars,
|
|
69
|
-
maxVisible = 3,
|
|
70
|
-
size = 'md',
|
|
71
|
-
customSize,
|
|
72
|
-
spacing = -8,
|
|
73
|
-
showOverflow = true,
|
|
74
|
-
overflowBackgroundColor,
|
|
75
|
-
overflowTextColor,
|
|
76
|
-
borderWidth = 2,
|
|
77
|
-
borderColor,
|
|
78
|
-
style,
|
|
79
|
-
testID,
|
|
80
|
-
}) => {
|
|
81
|
-
const tokens = useAppDesignTokens();
|
|
82
|
-
|
|
83
|
-
// Calculate visible avatars and overflow count
|
|
84
|
-
const visibleAvatars = avatars.slice(0, maxVisible);
|
|
85
|
-
const overflowCount = avatars.length - maxVisible;
|
|
86
|
-
|
|
87
|
-
// Size mapping for overflow text
|
|
88
|
-
const sizeMap = {
|
|
89
|
-
xs: 10,
|
|
90
|
-
sm: 12,
|
|
91
|
-
md: 14,
|
|
92
|
-
lg: 16,
|
|
93
|
-
xl: 18,
|
|
94
|
-
xxl: 20,
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
const textSize = sizeMap[size];
|
|
98
|
-
|
|
99
|
-
// Default colors for overflow
|
|
100
|
-
const defaultOverflowBackground = overflowBackgroundColor || tokens.colors.surfaceVariant;
|
|
101
|
-
const defaultOverflowText = overflowTextColor || tokens.colors.textPrimary;
|
|
102
|
-
|
|
103
|
-
const containerStyle: ViewStyle = {
|
|
104
|
-
flexDirection: 'row',
|
|
105
|
-
alignItems: 'center',
|
|
106
|
-
};
|
|
107
|
-
|
|
108
|
-
const avatarStyle: ViewStyle = {
|
|
109
|
-
marginLeft: spacing,
|
|
110
|
-
borderWidth,
|
|
111
|
-
borderColor: borderColor || tokens.colors.surface,
|
|
112
|
-
};
|
|
113
|
-
|
|
114
|
-
return (
|
|
115
|
-
<View style={[containerStyle, style]} testID={testID}>
|
|
116
|
-
{visibleAvatars.map((avatar, index) => (
|
|
117
|
-
<AtomicAvatar
|
|
118
|
-
key={avatar.id}
|
|
119
|
-
source={avatar.source}
|
|
120
|
-
name={avatar.name}
|
|
121
|
-
size={size}
|
|
122
|
-
customSize={customSize}
|
|
123
|
-
backgroundColor={avatar.backgroundColor}
|
|
124
|
-
textColor={avatar.textColor}
|
|
125
|
-
borderWidth={borderWidth}
|
|
126
|
-
borderColor={borderColor}
|
|
127
|
-
style={[
|
|
128
|
-
avatarStyle,
|
|
129
|
-
...(index === 0 ? [{ marginLeft: 0 }] : []), // First avatar has no left margin
|
|
130
|
-
]}
|
|
131
|
-
/>
|
|
132
|
-
))}
|
|
133
|
-
{showOverflow && overflowCount > 0 && (
|
|
134
|
-
<View
|
|
135
|
-
style={[
|
|
136
|
-
{
|
|
137
|
-
alignItems: 'center',
|
|
138
|
-
justifyContent: 'center',
|
|
139
|
-
width: customSize || 40,
|
|
140
|
-
height: customSize || 40,
|
|
141
|
-
borderRadius: (customSize || 40) / 2,
|
|
142
|
-
backgroundColor: defaultOverflowBackground,
|
|
143
|
-
borderWidth,
|
|
144
|
-
borderColor: borderColor || tokens.colors.surface,
|
|
145
|
-
marginLeft: spacing,
|
|
146
|
-
},
|
|
147
|
-
]}
|
|
148
|
-
>
|
|
149
|
-
<AtomicText
|
|
150
|
-
type="labelMedium"
|
|
151
|
-
color={defaultOverflowText}
|
|
152
|
-
style={{
|
|
153
|
-
fontWeight: tokens.typography.semibold,
|
|
154
|
-
fontSize: textSize,
|
|
155
|
-
}}
|
|
156
|
-
>
|
|
157
|
-
+{overflowCount}
|
|
158
|
-
</AtomicText>
|
|
159
|
-
</View>
|
|
160
|
-
)}
|
|
161
|
-
</View>
|
|
162
|
-
);
|
|
163
|
-
};
|
|
164
|
-
|
|
165
|
-
// =============================================================================
|
|
166
|
-
// EXPORTS
|
|
167
|
-
// =============================================================================
|
|
168
|
-
|
|
169
|
-
export default AtomicAvatarGroup;
|
|
@@ -1,232 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* AtomicBadge - Universal Badge Component
|
|
3
|
-
*
|
|
4
|
-
* Provides consistent badge/notification count display
|
|
5
|
-
* Theme: {{THEME_NAME}} ({{CATEGORY}} category)
|
|
6
|
-
*
|
|
7
|
-
* Atomic Design Level: ATOM
|
|
8
|
-
* Purpose: Display counts, notifications, status indicators
|
|
9
|
-
*
|
|
10
|
-
* Usage:
|
|
11
|
-
* - Notification counts
|
|
12
|
-
* - Cart item counts
|
|
13
|
-
* - Status indicators
|
|
14
|
-
* - Achievement badges
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
import React from 'react';
|
|
18
|
-
import { View, StyleSheet, StyleProp, ViewStyle, TextStyle } from 'react-native';
|
|
19
|
-
import { AtomicText } from './AtomicText';
|
|
20
|
-
import { useAppDesignTokens } from '@umituz/react-native-theme';
|
|
21
|
-
import type { DesignTokens } from '@umituz/react-native-theme';
|
|
22
|
-
|
|
23
|
-
// =============================================================================
|
|
24
|
-
// TYPE DEFINITIONS
|
|
25
|
-
// =============================================================================
|
|
26
|
-
|
|
27
|
-
export interface AtomicBadgeProps {
|
|
28
|
-
/** Badge content (number, text, or custom element) */
|
|
29
|
-
children: React.ReactNode;
|
|
30
|
-
/** Size variant */
|
|
31
|
-
size?: 'xs' | 'sm' | 'md' | 'lg';
|
|
32
|
-
/** Color variant */
|
|
33
|
-
variant?: 'primary' | 'secondary' | 'success' | 'warning' | 'error' | 'info';
|
|
34
|
-
/** Shape variant */
|
|
35
|
-
shape?: 'circle' | 'rounded' | 'square';
|
|
36
|
-
/** Maximum value to display (e.g., 99+) */
|
|
37
|
-
max?: number;
|
|
38
|
-
/** Show badge even when count is 0 */
|
|
39
|
-
showZero?: boolean;
|
|
40
|
-
/** Container style override */
|
|
41
|
-
style?: StyleProp<ViewStyle>;
|
|
42
|
-
/** Text style override */
|
|
43
|
-
textStyle?: StyleProp<TextStyle>;
|
|
44
|
-
/** Minimum width */
|
|
45
|
-
minWidth?: number;
|
|
46
|
-
/** Maximum width */
|
|
47
|
-
maxWidth?: number;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// =============================================================================
|
|
51
|
-
// SIZE CONFIGURATION
|
|
52
|
-
// =============================================================================
|
|
53
|
-
|
|
54
|
-
const getSizeConfig = (tokens: DesignTokens) => ({
|
|
55
|
-
xs: {
|
|
56
|
-
minHeight: tokens.spacing.sm,
|
|
57
|
-
paddingHorizontal: tokens.spacing.xs,
|
|
58
|
-
fontSize: tokens.typography.labelSmall.fontSize,
|
|
59
|
-
borderRadius: tokens.borders.radius.sm,
|
|
60
|
-
},
|
|
61
|
-
sm: {
|
|
62
|
-
minHeight: tokens.spacing.md,
|
|
63
|
-
paddingHorizontal: tokens.spacing.sm,
|
|
64
|
-
fontSize: tokens.typography.bodySmall.fontSize,
|
|
65
|
-
borderRadius: tokens.borders.radius.md,
|
|
66
|
-
},
|
|
67
|
-
md: {
|
|
68
|
-
minHeight: tokens.spacing.lg,
|
|
69
|
-
paddingHorizontal: tokens.spacing.sm,
|
|
70
|
-
fontSize: tokens.typography.bodyMedium.fontSize,
|
|
71
|
-
borderRadius: tokens.borders.radius.md,
|
|
72
|
-
},
|
|
73
|
-
lg: {
|
|
74
|
-
minHeight: tokens.spacing.xl,
|
|
75
|
-
paddingHorizontal: tokens.spacing.md,
|
|
76
|
-
fontSize: tokens.typography.bodyLarge.fontSize,
|
|
77
|
-
borderRadius: tokens.borders.radius.lg,
|
|
78
|
-
},
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
// =============================================================================
|
|
82
|
-
// COMPONENT IMPLEMENTATION
|
|
83
|
-
// =============================================================================
|
|
84
|
-
|
|
85
|
-
export const AtomicBadge: React.FC<AtomicBadgeProps> = ({
|
|
86
|
-
children,
|
|
87
|
-
size = 'md',
|
|
88
|
-
variant = 'primary',
|
|
89
|
-
shape = 'circle',
|
|
90
|
-
max,
|
|
91
|
-
showZero = false,
|
|
92
|
-
style,
|
|
93
|
-
textStyle,
|
|
94
|
-
minWidth,
|
|
95
|
-
maxWidth,
|
|
96
|
-
}) => {
|
|
97
|
-
const tokens = useAppDesignTokens();
|
|
98
|
-
const styles = getStyles(tokens);
|
|
99
|
-
|
|
100
|
-
const sizeConfig = getSizeConfig(tokens)[size as 'xs' | 'sm' | 'md' | 'lg'];
|
|
101
|
-
const colors = getVariantColors(tokens, variant as 'primary' | 'secondary' | 'success' | 'warning' | 'error' | 'info');
|
|
102
|
-
const borderRadius = getBorderRadius(shape as 'circle' | 'rounded' | 'square', sizeConfig.borderRadius, tokens);
|
|
103
|
-
|
|
104
|
-
// Handle max value display
|
|
105
|
-
const displayValue = React.useMemo(() => {
|
|
106
|
-
if (typeof children === 'number') {
|
|
107
|
-
if (max && children > max) {
|
|
108
|
-
return `${max}+`;
|
|
109
|
-
}
|
|
110
|
-
return children.toString();
|
|
111
|
-
}
|
|
112
|
-
return children;
|
|
113
|
-
}, [children, max]);
|
|
114
|
-
|
|
115
|
-
// Don't render if count is 0 and showZero is false
|
|
116
|
-
if (typeof children === 'number' && children === 0 && !showZero) {
|
|
117
|
-
return null;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
const containerStyle = [
|
|
121
|
-
styles.container,
|
|
122
|
-
{
|
|
123
|
-
minHeight: sizeConfig.minHeight,
|
|
124
|
-
paddingHorizontal: sizeConfig.paddingHorizontal,
|
|
125
|
-
borderRadius,
|
|
126
|
-
backgroundColor: colors.background,
|
|
127
|
-
minWidth: minWidth || sizeConfig.minHeight,
|
|
128
|
-
maxWidth,
|
|
129
|
-
},
|
|
130
|
-
style,
|
|
131
|
-
];
|
|
132
|
-
|
|
133
|
-
const textStyleFinal = StyleSheet.flatten([
|
|
134
|
-
styles.text,
|
|
135
|
-
{
|
|
136
|
-
fontSize: sizeConfig.fontSize,
|
|
137
|
-
},
|
|
138
|
-
textStyle,
|
|
139
|
-
]);
|
|
140
|
-
|
|
141
|
-
return (
|
|
142
|
-
<View style={containerStyle}>
|
|
143
|
-
<AtomicText
|
|
144
|
-
type="bodySmall"
|
|
145
|
-
color={colors.text}
|
|
146
|
-
style={textStyleFinal}
|
|
147
|
-
numberOfLines={1}
|
|
148
|
-
>
|
|
149
|
-
{displayValue}
|
|
150
|
-
</AtomicText>
|
|
151
|
-
</View>
|
|
152
|
-
);
|
|
153
|
-
};
|
|
154
|
-
|
|
155
|
-
// =============================================================================
|
|
156
|
-
// HELPER FUNCTIONS
|
|
157
|
-
// =============================================================================
|
|
158
|
-
|
|
159
|
-
const getVariantColors = (tokens: ReturnType<typeof useAppDesignTokens>, variant: AtomicBadgeProps['variant']) => {
|
|
160
|
-
switch (variant) {
|
|
161
|
-
case 'primary':
|
|
162
|
-
return {
|
|
163
|
-
background: tokens.colors.primary,
|
|
164
|
-
text: tokens.colors.textInverse,
|
|
165
|
-
};
|
|
166
|
-
case 'secondary':
|
|
167
|
-
return {
|
|
168
|
-
background: tokens.colors.secondary,
|
|
169
|
-
text: tokens.colors.textInverse,
|
|
170
|
-
};
|
|
171
|
-
case 'success':
|
|
172
|
-
return {
|
|
173
|
-
background: tokens.colors.success,
|
|
174
|
-
text: tokens.colors.textInverse,
|
|
175
|
-
};
|
|
176
|
-
case 'warning':
|
|
177
|
-
return {
|
|
178
|
-
background: tokens.colors.warning,
|
|
179
|
-
text: tokens.colors.textInverse,
|
|
180
|
-
};
|
|
181
|
-
case 'error':
|
|
182
|
-
return {
|
|
183
|
-
background: tokens.colors.error,
|
|
184
|
-
text: tokens.colors.textInverse,
|
|
185
|
-
};
|
|
186
|
-
case 'info':
|
|
187
|
-
return {
|
|
188
|
-
background: tokens.colors.info,
|
|
189
|
-
text: tokens.colors.textInverse,
|
|
190
|
-
};
|
|
191
|
-
default:
|
|
192
|
-
return {
|
|
193
|
-
background: tokens.colors.primary,
|
|
194
|
-
text: tokens.colors.textInverse,
|
|
195
|
-
};
|
|
196
|
-
}
|
|
197
|
-
};
|
|
198
|
-
|
|
199
|
-
const getBorderRadius = (shape: AtomicBadgeProps['shape'], defaultRadius: number, tokens: ReturnType<typeof useAppDesignTokens>): number => {
|
|
200
|
-
switch (shape) {
|
|
201
|
-
case 'circle':
|
|
202
|
-
return tokens.borders.radius.full; // Very large radius for circle
|
|
203
|
-
case 'square':
|
|
204
|
-
return tokens.borders.radius.sm;
|
|
205
|
-
case 'rounded':
|
|
206
|
-
default:
|
|
207
|
-
return defaultRadius;
|
|
208
|
-
}
|
|
209
|
-
};
|
|
210
|
-
|
|
211
|
-
// =============================================================================
|
|
212
|
-
// STYLES
|
|
213
|
-
// =============================================================================
|
|
214
|
-
|
|
215
|
-
const getStyles = (tokens: ReturnType<typeof useAppDesignTokens>) =>
|
|
216
|
-
StyleSheet.create({
|
|
217
|
-
container: {
|
|
218
|
-
justifyContent: 'center',
|
|
219
|
-
alignItems: 'center',
|
|
220
|
-
alignSelf: 'flex-start',
|
|
221
|
-
},
|
|
222
|
-
text: {
|
|
223
|
-
fontWeight: tokens.typography.semibold,
|
|
224
|
-
textAlign: 'center',
|
|
225
|
-
},
|
|
226
|
-
});
|
|
227
|
-
|
|
228
|
-
// =============================================================================
|
|
229
|
-
// EXPORTS
|
|
230
|
-
// =============================================================================
|
|
231
|
-
|
|
232
|
-
export default AtomicBadge;
|
|
@@ -1,236 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { StyleSheet, StyleProp, ViewStyle, TextStyle, 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
|
-
import type { IconName } from '@umituz/react-native-icon';
|
|
7
|
-
|
|
8
|
-
export type ButtonVariant = 'primary' | 'secondary' | 'outline' | 'text' | 'danger';
|
|
9
|
-
export type ButtonSize = 'sm' | 'md' | 'lg';
|
|
10
|
-
|
|
11
|
-
export interface AtomicButtonProps {
|
|
12
|
-
title?: string;
|
|
13
|
-
children?: React.ReactNode;
|
|
14
|
-
onPress: () => void;
|
|
15
|
-
variant?: ButtonVariant;
|
|
16
|
-
size?: ButtonSize;
|
|
17
|
-
disabled?: boolean;
|
|
18
|
-
icon?: IconName;
|
|
19
|
-
fullWidth?: boolean;
|
|
20
|
-
style?: StyleProp<ViewStyle>;
|
|
21
|
-
textStyle?: StyleProp<TextStyle>;
|
|
22
|
-
testID?: string;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export const AtomicButton: React.FC<AtomicButtonProps> = ({
|
|
26
|
-
title,
|
|
27
|
-
children,
|
|
28
|
-
onPress,
|
|
29
|
-
variant = 'primary',
|
|
30
|
-
size = 'md',
|
|
31
|
-
disabled = false,
|
|
32
|
-
icon,
|
|
33
|
-
fullWidth = false,
|
|
34
|
-
style,
|
|
35
|
-
textStyle,
|
|
36
|
-
testID,
|
|
37
|
-
}) => {
|
|
38
|
-
const tokens = useAppDesignTokens();
|
|
39
|
-
|
|
40
|
-
const handlePress = () => {
|
|
41
|
-
if (!disabled) {
|
|
42
|
-
onPress();
|
|
43
|
-
}
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
// Size configurations
|
|
47
|
-
const sizeConfig = {
|
|
48
|
-
sm: {
|
|
49
|
-
paddingVertical: tokens.spacing.xs,
|
|
50
|
-
paddingHorizontal: tokens.spacing.sm,
|
|
51
|
-
fontSize: tokens.typography.bodySmall.fontSize,
|
|
52
|
-
iconSize: 16,
|
|
53
|
-
minHeight: 32,
|
|
54
|
-
},
|
|
55
|
-
md: {
|
|
56
|
-
paddingVertical: tokens.spacing.sm,
|
|
57
|
-
paddingHorizontal: tokens.spacing.md,
|
|
58
|
-
fontSize: tokens.typography.bodyMedium.fontSize,
|
|
59
|
-
iconSize: 20,
|
|
60
|
-
minHeight: 44,
|
|
61
|
-
},
|
|
62
|
-
lg: {
|
|
63
|
-
paddingVertical: tokens.spacing.md,
|
|
64
|
-
paddingHorizontal: tokens.spacing.lg,
|
|
65
|
-
fontSize: tokens.typography.bodyLarge.fontSize,
|
|
66
|
-
iconSize: 24,
|
|
67
|
-
minHeight: 52,
|
|
68
|
-
},
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
const config = sizeConfig[size];
|
|
72
|
-
|
|
73
|
-
// Variant styles
|
|
74
|
-
const getVariantStyles = () => {
|
|
75
|
-
const baseStyle: ViewStyle = {
|
|
76
|
-
backgroundColor: tokens.colors.primary,
|
|
77
|
-
borderWidth: 0,
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
const baseTextStyle: TextStyle = {
|
|
81
|
-
color: tokens.colors.textInverse,
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
switch (variant) {
|
|
85
|
-
case 'primary':
|
|
86
|
-
return {
|
|
87
|
-
container: {
|
|
88
|
-
...baseStyle,
|
|
89
|
-
backgroundColor: tokens.colors.primary,
|
|
90
|
-
},
|
|
91
|
-
text: {
|
|
92
|
-
...baseTextStyle,
|
|
93
|
-
color: tokens.colors.textInverse,
|
|
94
|
-
},
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
case 'secondary':
|
|
98
|
-
return {
|
|
99
|
-
container: {
|
|
100
|
-
...baseStyle,
|
|
101
|
-
backgroundColor: tokens.colors.surfaceSecondary,
|
|
102
|
-
},
|
|
103
|
-
text: {
|
|
104
|
-
...baseTextStyle,
|
|
105
|
-
color: tokens.colors.textPrimary,
|
|
106
|
-
},
|
|
107
|
-
};
|
|
108
|
-
|
|
109
|
-
case 'outline':
|
|
110
|
-
return {
|
|
111
|
-
container: {
|
|
112
|
-
...baseStyle,
|
|
113
|
-
backgroundColor: 'transparent',
|
|
114
|
-
borderWidth: 1,
|
|
115
|
-
borderColor: tokens.colors.border,
|
|
116
|
-
},
|
|
117
|
-
text: {
|
|
118
|
-
...baseTextStyle,
|
|
119
|
-
color: tokens.colors.textPrimary,
|
|
120
|
-
},
|
|
121
|
-
};
|
|
122
|
-
|
|
123
|
-
case 'text':
|
|
124
|
-
return {
|
|
125
|
-
container: {
|
|
126
|
-
...baseStyle,
|
|
127
|
-
backgroundColor: 'transparent',
|
|
128
|
-
},
|
|
129
|
-
text: {
|
|
130
|
-
...baseTextStyle,
|
|
131
|
-
color: tokens.colors.primary,
|
|
132
|
-
},
|
|
133
|
-
};
|
|
134
|
-
|
|
135
|
-
case 'danger':
|
|
136
|
-
return {
|
|
137
|
-
container: {
|
|
138
|
-
...baseStyle,
|
|
139
|
-
backgroundColor: tokens.colors.error,
|
|
140
|
-
},
|
|
141
|
-
text: {
|
|
142
|
-
...baseTextStyle,
|
|
143
|
-
color: tokens.colors.textInverse,
|
|
144
|
-
},
|
|
145
|
-
};
|
|
146
|
-
|
|
147
|
-
default:
|
|
148
|
-
return {
|
|
149
|
-
container: baseStyle,
|
|
150
|
-
text: baseTextStyle,
|
|
151
|
-
};
|
|
152
|
-
}
|
|
153
|
-
};
|
|
154
|
-
|
|
155
|
-
const variantStyles = getVariantStyles();
|
|
156
|
-
|
|
157
|
-
const containerStyle: StyleProp<ViewStyle> = [
|
|
158
|
-
styles.button,
|
|
159
|
-
{
|
|
160
|
-
paddingVertical: config.paddingVertical,
|
|
161
|
-
paddingHorizontal: config.paddingHorizontal,
|
|
162
|
-
minHeight: config.minHeight,
|
|
163
|
-
borderRadius: tokens.borders.radius.md,
|
|
164
|
-
},
|
|
165
|
-
variantStyles.container,
|
|
166
|
-
fullWidth ? styles.fullWidth : undefined,
|
|
167
|
-
disabled ? styles.disabled : undefined,
|
|
168
|
-
style,
|
|
169
|
-
];
|
|
170
|
-
|
|
171
|
-
const buttonTextStyle: StyleProp<TextStyle> = [
|
|
172
|
-
{
|
|
173
|
-
fontSize: config.fontSize,
|
|
174
|
-
fontWeight: '600',
|
|
175
|
-
},
|
|
176
|
-
variantStyles.text,
|
|
177
|
-
disabled ? styles.disabledText : undefined,
|
|
178
|
-
textStyle,
|
|
179
|
-
];
|
|
180
|
-
|
|
181
|
-
const buttonText = title || children;
|
|
182
|
-
const showIcon = icon;
|
|
183
|
-
const iconColor = variantStyles.text.color;
|
|
184
|
-
|
|
185
|
-
return (
|
|
186
|
-
<TouchableOpacity
|
|
187
|
-
style={containerStyle}
|
|
188
|
-
onPress={handlePress}
|
|
189
|
-
activeOpacity={0.8}
|
|
190
|
-
disabled={disabled}
|
|
191
|
-
testID={testID}
|
|
192
|
-
>
|
|
193
|
-
<View style={styles.content}>
|
|
194
|
-
{showIcon ? (
|
|
195
|
-
<Icon
|
|
196
|
-
name={typeof icon === 'string' ? icon : String(icon)}
|
|
197
|
-
customSize={config.iconSize}
|
|
198
|
-
customColor={typeof iconColor === 'string' ? iconColor : undefined}
|
|
199
|
-
style={styles.icon}
|
|
200
|
-
/>
|
|
201
|
-
) : null}
|
|
202
|
-
|
|
203
|
-
<AtomicText style={buttonTextStyle}>
|
|
204
|
-
{buttonText}
|
|
205
|
-
</AtomicText>
|
|
206
|
-
</View>
|
|
207
|
-
</TouchableOpacity>
|
|
208
|
-
);
|
|
209
|
-
};
|
|
210
|
-
|
|
211
|
-
const styles = StyleSheet.create({
|
|
212
|
-
button: {
|
|
213
|
-
alignItems: 'center',
|
|
214
|
-
justifyContent: 'center',
|
|
215
|
-
flexDirection: 'row',
|
|
216
|
-
},
|
|
217
|
-
content: {
|
|
218
|
-
flexDirection: 'row',
|
|
219
|
-
alignItems: 'center',
|
|
220
|
-
justifyContent: 'center',
|
|
221
|
-
},
|
|
222
|
-
fullWidth: {
|
|
223
|
-
width: '100%',
|
|
224
|
-
},
|
|
225
|
-
disabled: {
|
|
226
|
-
opacity: 0.5,
|
|
227
|
-
},
|
|
228
|
-
disabledText: {
|
|
229
|
-
opacity: 0.7,
|
|
230
|
-
},
|
|
231
|
-
icon: {
|
|
232
|
-
marginRight: 8,
|
|
233
|
-
},
|
|
234
|
-
});
|
|
235
|
-
|
|
236
|
-
export type { AtomicButtonProps as ButtonProps };
|