@umituz/react-native-design-system 1.0.0

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.
Files changed (80) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +157 -0
  3. package/package.json +43 -0
  4. package/src/index.ts +345 -0
  5. package/src/presentation/atoms/AtomicAvatar.tsx +157 -0
  6. package/src/presentation/atoms/AtomicAvatarGroup.tsx +169 -0
  7. package/src/presentation/atoms/AtomicBadge.tsx +232 -0
  8. package/src/presentation/atoms/AtomicButton.tsx +124 -0
  9. package/src/presentation/atoms/AtomicCard.tsx +112 -0
  10. package/src/presentation/atoms/AtomicChip.tsx +223 -0
  11. package/src/presentation/atoms/AtomicDatePicker.tsx +347 -0
  12. package/src/presentation/atoms/AtomicDivider.tsx +114 -0
  13. package/src/presentation/atoms/AtomicFab.tsx +104 -0
  14. package/src/presentation/atoms/AtomicFilter.tsx +154 -0
  15. package/src/presentation/atoms/AtomicFormError.tsx +105 -0
  16. package/src/presentation/atoms/AtomicIcon.tsx +29 -0
  17. package/src/presentation/atoms/AtomicImage.tsx +149 -0
  18. package/src/presentation/atoms/AtomicInput.tsx +232 -0
  19. package/src/presentation/atoms/AtomicNumberInput.tsx +182 -0
  20. package/src/presentation/atoms/AtomicPicker.tsx +458 -0
  21. package/src/presentation/atoms/AtomicProgress.tsx +143 -0
  22. package/src/presentation/atoms/AtomicSearchBar.tsx +114 -0
  23. package/src/presentation/atoms/AtomicSkeleton.tsx +146 -0
  24. package/src/presentation/atoms/AtomicSort.tsx +145 -0
  25. package/src/presentation/atoms/AtomicSwitch.tsx +166 -0
  26. package/src/presentation/atoms/AtomicText.tsx +50 -0
  27. package/src/presentation/atoms/AtomicTextArea.tsx +198 -0
  28. package/src/presentation/atoms/AtomicTouchable.tsx +233 -0
  29. package/src/presentation/atoms/fab/styles/fabStyles.ts +69 -0
  30. package/src/presentation/atoms/fab/types/index.ts +88 -0
  31. package/src/presentation/atoms/filter/styles/filterStyles.ts +32 -0
  32. package/src/presentation/atoms/filter/types/index.ts +89 -0
  33. package/src/presentation/atoms/index.ts +378 -0
  34. package/src/presentation/atoms/input/hooks/useInputState.ts +15 -0
  35. package/src/presentation/atoms/input/styles/inputStyles.ts +66 -0
  36. package/src/presentation/atoms/input/types/index.ts +25 -0
  37. package/src/presentation/atoms/picker/styles/pickerStyles.ts +200 -0
  38. package/src/presentation/atoms/picker/types/index.ts +40 -0
  39. package/src/presentation/atoms/touchable/styles/touchableStyles.ts +71 -0
  40. package/src/presentation/atoms/touchable/types/index.ts +162 -0
  41. package/src/presentation/hooks/useAppDesignTokens.ts +78 -0
  42. package/src/presentation/hooks/useResponsive.ts +180 -0
  43. package/src/presentation/loading/index.ts +40 -0
  44. package/src/presentation/loading/presentation/components/LoadingSpinner.tsx +116 -0
  45. package/src/presentation/loading/presentation/components/LoadingState.tsx +200 -0
  46. package/src/presentation/loading/presentation/hooks/useLoading.ts +100 -0
  47. package/src/presentation/molecules/AtomicConfirmationModal.tsx +263 -0
  48. package/src/presentation/molecules/EmptyState.tsx +130 -0
  49. package/src/presentation/molecules/FormField.tsx +128 -0
  50. package/src/presentation/molecules/GridContainer.tsx +124 -0
  51. package/src/presentation/molecules/IconContainer.tsx +94 -0
  52. package/src/presentation/molecules/LanguageSwitcher.tsx +42 -0
  53. package/src/presentation/molecules/ListItem.tsx +36 -0
  54. package/src/presentation/molecules/ScreenHeader.tsx +140 -0
  55. package/src/presentation/molecules/SearchBar.tsx +85 -0
  56. package/src/presentation/molecules/SectionCard.tsx +74 -0
  57. package/src/presentation/molecules/SectionContainer.tsx +106 -0
  58. package/src/presentation/molecules/SectionHeader.tsx +125 -0
  59. package/src/presentation/molecules/confirmation-modal/styles/confirmationModalStyles.ts +133 -0
  60. package/src/presentation/molecules/confirmation-modal/types/index.ts +107 -0
  61. package/src/presentation/molecules/index.ts +42 -0
  62. package/src/presentation/molecules/languageswitcher/config/languageSwitcherConfig.ts +5 -0
  63. package/src/presentation/molecules/languageswitcher/hooks/useLanguageNavigation.ts +15 -0
  64. package/src/presentation/molecules/listitem/styles/listItemStyles.ts +19 -0
  65. package/src/presentation/molecules/listitem/types/index.ts +17 -0
  66. package/src/presentation/organisms/AppHeader.tsx +136 -0
  67. package/src/presentation/organisms/FormContainer.tsx +180 -0
  68. package/src/presentation/organisms/ScreenLayout.tsx +209 -0
  69. package/src/presentation/organisms/index.ts +25 -0
  70. package/src/presentation/tokens/AppDesignTokens.ts +57 -0
  71. package/src/presentation/tokens/commonStyles.ts +253 -0
  72. package/src/presentation/tokens/core/BaseTokens.ts +394 -0
  73. package/src/presentation/tokens/core/ColorPalette.ts +398 -0
  74. package/src/presentation/tokens/core/TokenFactory.ts +120 -0
  75. package/src/presentation/utils/platformConstants.ts +124 -0
  76. package/src/presentation/utils/responsive.ts +516 -0
  77. package/src/presentation/utils/variants/compound.ts +29 -0
  78. package/src/presentation/utils/variants/core.ts +39 -0
  79. package/src/presentation/utils/variants/helpers.ts +13 -0
  80. package/src/presentation/utils/variants.ts +3 -0
@@ -0,0 +1,157 @@
1
+ /**
2
+ * AtomicAvatar - Universal Avatar Component
3
+ *
4
+ * Displays user profile images with fallback to initials
5
+ * Theme: {{THEME_NAME}} ({{CATEGORY}} category)
6
+ *
7
+ * Atomic Design Level: ATOM
8
+ * Purpose: User profile image display
9
+ *
10
+ * Usage:
11
+ * - User profile pictures
12
+ * - Contact avatars
13
+ * - Group member avatars
14
+ * - Default user placeholders
15
+ */
16
+
17
+ import React from 'react';
18
+ import { View, StyleSheet, ViewStyle, ImageStyle } from 'react-native';
19
+ import { AtomicImage } from './AtomicImage';
20
+ import { AtomicText } from './AtomicText';
21
+ import { useAppDesignTokens } from '../hooks/useAppDesignTokens';
22
+
23
+ // =============================================================================
24
+ // TYPE DEFINITIONS
25
+ // =============================================================================
26
+
27
+ export interface AtomicAvatarProps {
28
+ /** Image source (URI or require) */
29
+ source?: { uri: string } | number;
30
+ /** User's name for fallback initials */
31
+ name?: string;
32
+ /** Size of the avatar */
33
+ size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'xxl';
34
+ /** Custom size in pixels */
35
+ customSize?: number;
36
+ /** Background color for fallback */
37
+ backgroundColor?: string;
38
+ /** Text color for initials */
39
+ textColor?: string;
40
+ /** Border radius (default: circular) */
41
+ borderRadius?: number;
42
+ /** Border width */
43
+ borderWidth?: number;
44
+ /** Border color */
45
+ borderColor?: string;
46
+ /** Style overrides */
47
+ style?: ViewStyle | ViewStyle[];
48
+ /** Image style overrides */
49
+ imageStyle?: ImageStyle | ImageStyle[];
50
+ /** Test ID for testing */
51
+ testID?: string;
52
+ }
53
+
54
+ // =============================================================================
55
+ // COMPONENT IMPLEMENTATION
56
+ // =============================================================================
57
+
58
+ export const AtomicAvatar: React.FC<AtomicAvatarProps> = ({
59
+ source,
60
+ name,
61
+ size = 'md',
62
+ customSize,
63
+ backgroundColor,
64
+ textColor,
65
+ borderRadius,
66
+ borderWidth = 0,
67
+ borderColor,
68
+ style,
69
+ imageStyle,
70
+ testID,
71
+ }) => {
72
+ const tokens = useAppDesignTokens();
73
+
74
+ const avatarSize = customSize || tokens.avatarSizes[size];
75
+ const avatarRadius = borderRadius ?? avatarSize / 2;
76
+
77
+ // Generate initials from name
78
+ const getInitials = (name: string): string => {
79
+ return name
80
+ .split(' ')
81
+ .map(word => word.charAt(0))
82
+ .join('')
83
+ .toUpperCase()
84
+ .slice(0, 2);
85
+ };
86
+
87
+ // Default colors
88
+ const defaultBackgroundColor = backgroundColor || tokens.colors.primary;
89
+ const defaultTextColor = textColor || tokens.colors.onPrimary;
90
+ const defaultBorderColor = borderColor || tokens.colors.border;
91
+
92
+ const avatarStyle: ViewStyle = {
93
+ width: avatarSize,
94
+ height: avatarSize,
95
+ borderRadius: avatarRadius,
96
+ backgroundColor: defaultBackgroundColor,
97
+ borderWidth,
98
+ borderColor: defaultBorderColor,
99
+ alignItems: 'center',
100
+ justifyContent: 'center',
101
+ overflow: 'hidden',
102
+ };
103
+
104
+ const imageStyleFinal: ImageStyle = {
105
+ width: avatarSize,
106
+ height: avatarSize,
107
+ borderRadius: avatarRadius,
108
+ };
109
+
110
+ // Font size based on avatar size
111
+ const getFontSize = (size: number): number => {
112
+ if (size <= 32) return 12;
113
+ if (size <= 48) return 16;
114
+ if (size <= 64) return 20;
115
+ return 24;
116
+ };
117
+
118
+ return (
119
+ <View style={[avatarStyle, style]} testID={testID}>
120
+ {source ? (
121
+ <AtomicImage
122
+ source={source}
123
+ style={StyleSheet.flatten([imageStyleFinal, imageStyle])}
124
+ resizeMode="cover"
125
+ />
126
+ ) : name ? (
127
+ <AtomicText
128
+ type="labelLarge"
129
+ color={defaultTextColor}
130
+ style={{
131
+ fontSize: getFontSize(avatarSize),
132
+ fontWeight: tokens.typography.semibold,
133
+ }}
134
+ >
135
+ {getInitials(name)}
136
+ </AtomicText>
137
+ ) : (
138
+ <AtomicText
139
+ type="labelLarge"
140
+ color={defaultTextColor}
141
+ style={{
142
+ fontSize: getFontSize(avatarSize),
143
+ fontWeight: tokens.typography.semibold,
144
+ }}
145
+ >
146
+ ?
147
+ </AtomicText>
148
+ )}
149
+ </View>
150
+ );
151
+ };
152
+
153
+ // =============================================================================
154
+ // EXPORTS
155
+ // =============================================================================
156
+
157
+ export default AtomicAvatar;
@@ -0,0 +1,169 @@
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 '../hooks/useAppDesignTokens';
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;
@@ -0,0 +1,232 @@
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 '../hooks/useAppDesignTokens';
21
+ import type { DesignTokens } from '../tokens/AppDesignTokens';
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];
101
+ const colors = getVariantColors(tokens, variant);
102
+ const borderRadius = getBorderRadius(shape, 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;
@@ -0,0 +1,124 @@
1
+ import React from 'react';
2
+ import { Button as PaperButton } from 'react-native-paper';
3
+ import { StyleSheet, StyleProp, ViewStyle, TextStyle } from 'react-native';
4
+ import Animated, { useSharedValue, useAnimatedStyle, withSpring } from 'react-native-reanimated';
5
+ import { Pressable } from 'react-native';
6
+
7
+ export type ButtonVariant = 'primary' | 'secondary' | 'outline' | 'text' | 'danger';
8
+ export type ButtonSize = 'sm' | 'md' | 'lg';
9
+
10
+ export interface AtomicButtonProps {
11
+ title?: string;
12
+ children?: React.ReactNode;
13
+ onPress: () => void;
14
+ variant?: ButtonVariant;
15
+ size?: ButtonSize;
16
+ disabled?: boolean;
17
+ loading?: boolean;
18
+ icon?: string;
19
+ fullWidth?: boolean;
20
+ style?: StyleProp<ViewStyle>;
21
+ textStyle?: StyleProp<TextStyle>;
22
+ testID?: string;
23
+ }
24
+
25
+ const AnimatedPressable = Animated.createAnimatedComponent(Pressable);
26
+
27
+ export const AtomicButton: React.FC<AtomicButtonProps> = ({
28
+ title,
29
+ children,
30
+ onPress,
31
+ variant = 'primary',
32
+ size = 'md',
33
+ disabled = false,
34
+ loading = false,
35
+ icon,
36
+ fullWidth = false,
37
+ style,
38
+ testID,
39
+ }) => {
40
+ // Animation
41
+ const scale = useSharedValue(1);
42
+
43
+ const animatedStyle = useAnimatedStyle(() => ({
44
+ transform: [{ scale: scale.value }],
45
+ }));
46
+
47
+ const handlePressIn = () => {
48
+ if (!disabled && !loading) {
49
+ scale.value = withSpring(0.95, {
50
+ damping: 15,
51
+ stiffness: 150,
52
+ });
53
+ }
54
+ };
55
+
56
+ const handlePressOut = () => {
57
+ scale.value = withSpring(1);
58
+ };
59
+
60
+ const handlePress = () => {
61
+ if (!disabled && !loading) {
62
+ onPress();
63
+ }
64
+ };
65
+
66
+ // Map variants to Paper modes
67
+ const getPaperMode = (): 'contained' | 'outlined' | 'text' | 'contained-tonal' | 'elevated' => {
68
+ switch (variant) {
69
+ case 'primary':
70
+ return 'contained';
71
+ case 'secondary':
72
+ return 'contained-tonal';
73
+ case 'outline':
74
+ return 'outlined';
75
+ case 'text':
76
+ return 'text';
77
+ case 'danger':
78
+ return 'contained';
79
+ default:
80
+ return 'contained';
81
+ }
82
+ };
83
+
84
+ // Map size to padding
85
+ const getContentStyle = () => {
86
+ const paddingMap = {
87
+ sm: { paddingVertical: 4, paddingHorizontal: 12 },
88
+ md: { paddingVertical: 8, paddingHorizontal: 16 },
89
+ lg: { paddingVertical: 12, paddingHorizontal: 20 },
90
+ };
91
+ return paddingMap[size];
92
+ };
93
+
94
+ const buttonStyle: StyleProp<ViewStyle> = StyleSheet.flatten([
95
+ fullWidth ? { width: '100%' } : undefined,
96
+ style,
97
+ ]);
98
+
99
+ const buttonText = title || children;
100
+
101
+ return (
102
+ <AnimatedPressable
103
+ style={animatedStyle}
104
+ onPressIn={handlePressIn}
105
+ onPressOut={handlePressOut}
106
+ onPress={handlePress}
107
+ >
108
+ <PaperButton
109
+ mode={getPaperMode()}
110
+ disabled={disabled}
111
+ loading={loading}
112
+ icon={icon}
113
+ style={buttonStyle}
114
+ contentStyle={getContentStyle()}
115
+ testID={testID}
116
+ buttonColor={variant === 'danger' ? '#DC2626' : undefined}
117
+ >
118
+ {buttonText}
119
+ </PaperButton>
120
+ </AnimatedPressable>
121
+ );
122
+ };
123
+
124
+ export type { AtomicButtonProps as ButtonProps };