@umituz/react-native-design-system 1.15.0 → 2.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 (87) hide show
  1. package/package.json +26 -19
  2. package/src/atoms/AtomicAvatar.tsx +161 -0
  3. package/src/atoms/AtomicButton.tsx +241 -0
  4. package/src/atoms/AtomicChip.tsx +226 -0
  5. package/src/atoms/AtomicDatePicker.tsx +255 -0
  6. package/src/atoms/AtomicFab.tsx +99 -0
  7. package/src/atoms/AtomicIcon.tsx +149 -0
  8. package/src/atoms/AtomicInput.tsx +308 -0
  9. package/src/atoms/AtomicPicker.tsx +310 -0
  10. package/src/atoms/AtomicProgress.tsx +149 -0
  11. package/src/atoms/AtomicText.tsx +55 -0
  12. package/src/atoms/__tests__/AtomicButton.test.tsx +107 -0
  13. package/src/atoms/__tests__/AtomicIcon.test.tsx +110 -0
  14. package/src/atoms/__tests__/AtomicInput.test.tsx +195 -0
  15. package/src/atoms/datepicker/components/DatePickerButton.tsx +112 -0
  16. package/src/atoms/datepicker/components/DatePickerModal.tsx +143 -0
  17. package/src/atoms/fab/styles/fabStyles.ts +98 -0
  18. package/src/atoms/fab/types/index.ts +88 -0
  19. package/src/atoms/index.ts +70 -0
  20. package/src/atoms/input/hooks/useInputState.ts +63 -0
  21. package/src/atoms/input/styles/inputStylesHelper.ts +120 -0
  22. package/src/atoms/picker/components/PickerChips.tsx +57 -0
  23. package/src/atoms/picker/components/PickerModal.tsx +214 -0
  24. package/src/atoms/picker/styles/pickerStyles.ts +223 -0
  25. package/src/atoms/picker/types/index.ts +42 -0
  26. package/src/index.ts +133 -56
  27. package/src/molecules/ConfirmationModal.tsx +42 -0
  28. package/src/molecules/ConfirmationModalContent.tsx +87 -0
  29. package/src/molecules/ConfirmationModalMain.tsx +91 -0
  30. package/src/molecules/FormField.tsx +155 -0
  31. package/src/molecules/IconContainer.tsx +79 -0
  32. package/src/molecules/ListItem.tsx +35 -0
  33. package/src/molecules/ScreenHeader.tsx +171 -0
  34. package/src/molecules/SearchBar.tsx +198 -0
  35. package/src/molecules/confirmation-modal/components.tsx +94 -0
  36. package/src/molecules/confirmation-modal/index.ts +7 -0
  37. package/src/molecules/confirmation-modal/styles/confirmationModalStyles.ts +133 -0
  38. package/src/molecules/confirmation-modal/types/index.ts +41 -0
  39. package/src/molecules/confirmation-modal/useConfirmationModal.ts +50 -0
  40. package/src/molecules/index.ts +19 -0
  41. package/src/molecules/listitem/index.ts +6 -0
  42. package/src/molecules/listitem/styles/listItemStyles.ts +37 -0
  43. package/src/molecules/listitem/types/index.ts +21 -0
  44. package/src/organisms/AppHeader.tsx +136 -0
  45. package/src/organisms/FormContainer.tsx +169 -0
  46. package/src/organisms/ScreenLayout.tsx +183 -0
  47. package/src/organisms/index.ts +31 -0
  48. package/src/responsive/config.ts +139 -0
  49. package/src/responsive/deviceDetection.ts +155 -0
  50. package/src/responsive/gridUtils.ts +79 -0
  51. package/src/responsive/index.ts +52 -0
  52. package/src/responsive/platformConstants.ts +98 -0
  53. package/src/responsive/responsive.ts +61 -0
  54. package/src/responsive/responsiveLayout.ts +137 -0
  55. package/src/responsive/responsiveSizing.ts +134 -0
  56. package/src/responsive/useResponsive.ts +140 -0
  57. package/src/responsive/validation.ts +158 -0
  58. package/src/theme/core/BaseTokens.ts +42 -0
  59. package/src/theme/core/ColorPalette.ts +29 -0
  60. package/src/theme/core/CustomColors.ts +122 -0
  61. package/src/theme/core/NavigationTheme.ts +72 -0
  62. package/src/theme/core/TokenFactory.ts +103 -0
  63. package/src/theme/core/colors/ColorUtils.ts +53 -0
  64. package/src/theme/core/colors/DarkColors.ts +146 -0
  65. package/src/theme/core/colors/LightColors.ts +146 -0
  66. package/src/theme/core/constants/DesignConstants.ts +31 -0
  67. package/src/theme/core/themes.ts +118 -0
  68. package/src/theme/core/tokens/BaseTokens.ts +144 -0
  69. package/src/theme/core/tokens/Borders.ts +43 -0
  70. package/src/theme/core/tokens/Sizes.ts +51 -0
  71. package/src/theme/core/tokens/Spacing.ts +38 -0
  72. package/src/theme/core/tokens/Typography.ts +143 -0
  73. package/src/theme/hooks/useAppDesignTokens.ts +45 -0
  74. package/src/theme/hooks/useCommonStyles.ts +248 -0
  75. package/src/theme/hooks/useThemedStyles.ts +68 -0
  76. package/src/theme/index.ts +94 -0
  77. package/src/theme/infrastructure/globalThemeStore.ts +69 -0
  78. package/src/theme/infrastructure/storage/ThemeStorage.ts +93 -0
  79. package/src/theme/infrastructure/stores/themeStore.ts +109 -0
  80. package/src/typography/__tests__/colorValidationUtils.test.ts +180 -0
  81. package/src/typography/__tests__/textColorUtils.test.ts +185 -0
  82. package/src/typography/__tests__/textStyleUtils.test.ts +168 -0
  83. package/src/typography/domain/entities/TypographyTypes.ts +88 -0
  84. package/src/typography/index.ts +53 -0
  85. package/src/typography/presentation/utils/colorValidationUtils.ts +133 -0
  86. package/src/typography/presentation/utils/textColorUtils.ts +205 -0
  87. package/src/typography/presentation/utils/textStyleUtils.ts +159 -0
@@ -0,0 +1,310 @@
1
+ /**
2
+ * AtomicPicker Component
3
+ *
4
+ * A reusable option picker/dropdown component for selecting from a list of options.
5
+ *
6
+ * Features:
7
+ * - Single and multi-select support
8
+ * - Modal display mode (full-screen on mobile)
9
+ * - Optional search/filter capability
10
+ * - Error and disabled states
11
+ * - Theme-aware styling
12
+ * - Icons for options
13
+ * - Clearable selection
14
+ * - react-hook-form integration ready
15
+ *
16
+ * Architecture:
17
+ * - Follows AtomicButton pattern with separated types and styles
18
+ * - Uses helper functions from picker/styles/pickerStyles.ts
19
+ * - Types defined in picker/types/index.ts
20
+ * - Zero inline StyleSheet.create()
21
+ *
22
+ * Usage:
23
+ * ```tsx
24
+ * const [partyType, setPartyType] = useState('birthday');
25
+ *
26
+ * <AtomicPicker
27
+ * value={partyType}
28
+ * onChange={setPartyType}
29
+ * options={[
30
+ * { label: 'Birthday Party', value: 'birthday', icon: 'cake' },
31
+ * { label: 'Wedding', value: 'wedding', icon: 'heart' },
32
+ * { label: 'Corporate Event', value: 'corporate', icon: 'briefcase' },
33
+ * ]}
34
+ * label="Party Type"
35
+ * placeholder="Select party type"
36
+ * searchable
37
+ * />
38
+ * ```
39
+ *
40
+ * @module AtomicPicker
41
+ */
42
+
43
+ import React, { useState, useMemo } from 'react';
44
+ import {
45
+ View,
46
+ TouchableOpacity,
47
+ StyleSheet,
48
+ } from 'react-native';
49
+ import { useAppDesignTokens } from '../theme';
50
+ import { AtomicPickerProps, PickerOption } from './picker/types';
51
+ import { AtomicIcon } from './AtomicIcon';
52
+ import { AtomicText } from './AtomicText';
53
+ import { PickerModal } from './picker/components/PickerModal';
54
+ import { PickerChips } from './picker/components/PickerChips';
55
+ import {
56
+ getPickerContainerStyles,
57
+ getPickerLabelStyles,
58
+ getPickerPlaceholderStyles,
59
+ getPickerValueStyles,
60
+ getPickerErrorStyles,
61
+ } from './picker/styles/pickerStyles';
62
+
63
+ export type { AtomicPickerProps, PickerOption, PickerSize } from './picker/types';
64
+
65
+ /**
66
+ * AtomicPicker - Universal option picker component
67
+ *
68
+ * Displays a button that opens a modal for selection.
69
+ * Supports single/multi-select, search, and custom rendering.
70
+ */
71
+ export const AtomicPicker: React.FC<AtomicPickerProps> = ({
72
+ value,
73
+ onChange,
74
+ options,
75
+ label,
76
+ placeholder = 'Select...',
77
+ error,
78
+ disabled = false,
79
+ multiple = false,
80
+ searchable = false,
81
+ clearable = false,
82
+ autoClose = true,
83
+ color = 'primary',
84
+ size = 'md',
85
+ modalTitle,
86
+ emptyMessage = 'No options available',
87
+ clearAccessibilityLabel = 'Clear selection',
88
+ closeAccessibilityLabel = 'Close picker',
89
+ style,
90
+ labelStyle,
91
+ testID,
92
+ }) => {
93
+ const tokens = useAppDesignTokens();
94
+
95
+ const [modalVisible, setModalVisible] = useState(false);
96
+ const [searchQuery, setSearchQuery] = useState('');
97
+
98
+ // Get style helpers with design tokens
99
+ const containerStyles = getPickerContainerStyles(tokens);
100
+ const labelStyles = getPickerLabelStyles(tokens);
101
+ const placeholderStyles = getPickerPlaceholderStyles(tokens);
102
+ const valueStyles = getPickerValueStyles(tokens);
103
+ const errorStyles = getPickerErrorStyles(tokens);
104
+
105
+ /**
106
+ * Normalize value to array for consistent handling
107
+ */
108
+ const selectedValues = useMemo(() => {
109
+ if (multiple) {
110
+ return Array.isArray(value) ? value : [];
111
+ }
112
+ return value ? [value as string] : [];
113
+ }, [value, multiple]);
114
+
115
+ /**
116
+ * Get selected option objects
117
+ */
118
+ const selectedOptions = useMemo(() => {
119
+ return options.filter((opt) => selectedValues.includes(opt.value));
120
+ }, [options, selectedValues]);
121
+
122
+ /**
123
+ * Filter options based on search query
124
+ */
125
+ const filteredOptions = useMemo(() => {
126
+ if (!searchQuery.trim()) return options;
127
+
128
+ const query = searchQuery.toLowerCase();
129
+ return options.filter(
130
+ (opt) =>
131
+ opt.label.toLowerCase().includes(query) ||
132
+ opt.description?.toLowerCase().includes(query)
133
+ );
134
+ }, [options, searchQuery]);
135
+
136
+ /**
137
+ * Format display text for selected value(s)
138
+ */
139
+ const displayText = useMemo(() => {
140
+ if (selectedOptions.length === 0) {
141
+ return placeholder;
142
+ }
143
+
144
+ if (multiple) {
145
+ return selectedOptions.length === 1
146
+ ? selectedOptions[0].label
147
+ : `${selectedOptions.length} selected`;
148
+ }
149
+
150
+ return selectedOptions[0]?.label || placeholder;
151
+ }, [selectedOptions, placeholder, multiple]);
152
+
153
+ /**
154
+ * Handle modal open
155
+ */
156
+ const openModal = () => {
157
+ if (disabled) return;
158
+ setModalVisible(true);
159
+ setSearchQuery('');
160
+ };
161
+
162
+ /**
163
+ * Handle modal close
164
+ */
165
+ const closeModal = () => {
166
+ setModalVisible(false);
167
+ setSearchQuery('');
168
+ };
169
+
170
+ /**
171
+ * Handle option selection
172
+ */
173
+ const handleSelect = (optionValue: string) => {
174
+ if (multiple) {
175
+ const newValues = selectedValues.includes(optionValue)
176
+ ? selectedValues.filter((v) => v !== optionValue)
177
+ : [...selectedValues, optionValue];
178
+ onChange(newValues);
179
+ } else {
180
+ onChange(optionValue);
181
+ if (autoClose) {
182
+ closeModal();
183
+ }
184
+ }
185
+ };
186
+
187
+ /**
188
+ * Handle clear selection
189
+ */
190
+ const handleClear = () => {
191
+ onChange(multiple ? [] : '');
192
+ };
193
+
194
+ /**
195
+ * Handle search query change
196
+ */
197
+ const handleSearch = (query: string) => {
198
+ setSearchQuery(query);
199
+ };
200
+
201
+ /**
202
+ * Check if option is selected
203
+ */
204
+ const isSelected = (optionValue: string): boolean => {
205
+ return selectedValues.includes(optionValue);
206
+ };
207
+
208
+ /**
209
+ * Handle chip removal
210
+ */
211
+ const handleChipRemove = (value: string) => {
212
+ handleSelect(value);
213
+ };
214
+
215
+ const pickerContainerStyle = StyleSheet.flatten([
216
+ containerStyles.base,
217
+ containerStyles.size[size],
218
+ error ? containerStyles.state.error : undefined,
219
+ disabled ? containerStyles.state.disabled : undefined,
220
+ style,
221
+ ]);
222
+
223
+ const pickerLabelStyle = StyleSheet.flatten([
224
+ labelStyles.base,
225
+ labelStyles.size[size],
226
+ labelStyle,
227
+ ]);
228
+
229
+ const pickerValueStyle = StyleSheet.flatten([
230
+ selectedOptions.length > 0 ? valueStyles.base : placeholderStyles.base,
231
+ selectedOptions.length > 0
232
+ ? valueStyles.size[size]
233
+ : placeholderStyles.size[size],
234
+ ]);
235
+
236
+ return (
237
+ <View>
238
+ {/* Label */}
239
+ {label && <AtomicText style={pickerLabelStyle}>{label}</AtomicText>}
240
+
241
+ {/* Picker Button */}
242
+ <TouchableOpacity
243
+ onPress={openModal}
244
+ disabled={disabled}
245
+ accessibilityRole="button"
246
+ accessibilityLabel={label || placeholder}
247
+ accessibilityState={{ disabled }}
248
+ testID={testID}
249
+ style={pickerContainerStyle}
250
+ >
251
+ {/* Display Text */}
252
+ <AtomicText style={pickerValueStyle} numberOfLines={1}>
253
+ {displayText}
254
+ </AtomicText>
255
+
256
+ {/* Icons */}
257
+ <View style={{ flexDirection: 'row', alignItems: 'center', gap: tokens.spacing.xs }}>
258
+ {/* Clear Button */}
259
+ {clearable && selectedOptions.length > 0 && !disabled && (
260
+ <TouchableOpacity
261
+ onPress={handleClear}
262
+ hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }}
263
+ accessibilityRole="button"
264
+ accessibilityLabel={clearAccessibilityLabel}
265
+ testID={`${testID}-clear`}
266
+ >
267
+ <AtomicIcon name="X" size="sm" color="secondary" />
268
+ </TouchableOpacity>
269
+ )}
270
+
271
+ {/* Dropdown Icon */}
272
+ <AtomicIcon
273
+ name={modalVisible ? 'ChevronUp' : 'ChevronDown'}
274
+ size="sm"
275
+ color={disabled ? 'surfaceVariant' : 'secondary'}
276
+ />
277
+ </View>
278
+ </TouchableOpacity>
279
+
280
+ {/* Selected Chips (Multi-select) */}
281
+ <PickerChips
282
+ selectedOptions={selectedOptions}
283
+ onRemoveChip={handleChipRemove}
284
+ testID={testID}
285
+ />
286
+
287
+ {/* Error Message */}
288
+ {error && <AtomicText style={errorStyles}>{error}</AtomicText>}
289
+
290
+ {/* Selection Modal */}
291
+ <PickerModal
292
+ visible={modalVisible}
293
+ onClose={closeModal}
294
+ options={options}
295
+ selectedValues={selectedValues}
296
+ onSelect={handleSelect}
297
+ title={modalTitle || label}
298
+ searchable={searchable}
299
+ searchQuery={searchQuery}
300
+ onSearchChange={handleSearch}
301
+ filteredOptions={filteredOptions}
302
+ multiple={multiple}
303
+ emptyMessage={emptyMessage}
304
+ searchPlaceholder="Search..."
305
+ closeAccessibilityLabel={closeAccessibilityLabel}
306
+ testID={testID}
307
+ />
308
+ </View>
309
+ );
310
+ };
@@ -0,0 +1,149 @@
1
+ /**
2
+ * AtomicProgress - Universal Progress Bar Component
3
+ *
4
+ * Displays progress bars for completion tracking
5
+ * Theme: {{THEME_NAME}} ({{CATEGORY}} category)
6
+ *
7
+ * Atomic Design Level: ATOM
8
+ * Purpose: Progress indication and completion tracking
9
+ *
10
+ * Usage:
11
+ * - File upload progress
12
+ * - Task completion progress
13
+ * - Achievement progress
14
+ * - Form completion
15
+ */
16
+
17
+ import React from 'react';
18
+ import { View, StyleSheet, ViewStyle, DimensionValue, Text } from 'react-native';
19
+ import { useAppDesignTokens } from '../theme';
20
+
21
+ // =============================================================================
22
+ // TYPE DEFINITIONS
23
+ // =============================================================================
24
+
25
+ export interface AtomicProgressProps {
26
+ /** Progress value (0-100) */
27
+ value: number;
28
+ /** Progress bar height */
29
+ height?: number;
30
+ /** Progress bar width */
31
+ width?: number | string;
32
+ /** Progress bar color */
33
+ color?: string;
34
+ /** Background color */
35
+ backgroundColor?: string;
36
+ /** Progress bar shape */
37
+ shape?: 'rounded' | 'square';
38
+ /** Whether to show percentage text */
39
+ showPercentage?: boolean;
40
+ /** Whether to show value text */
41
+ showValue?: boolean;
42
+ /** Custom text color */
43
+ textColor?: string;
44
+ /** Style overrides */
45
+ style?: ViewStyle | ViewStyle[];
46
+ /** Test ID for testing */
47
+ testID?: string;
48
+ }
49
+
50
+ // =============================================================================
51
+ // COMPONENT IMPLEMENTATION
52
+ // =============================================================================
53
+
54
+ export const AtomicProgress: React.FC<AtomicProgressProps> = ({
55
+ value,
56
+ height = 8,
57
+ width = '100%',
58
+ color,
59
+ backgroundColor,
60
+ shape = 'rounded',
61
+ showPercentage = false,
62
+ showValue = false,
63
+ textColor,
64
+ style,
65
+ testID,
66
+ }) => {
67
+ const tokens = useAppDesignTokens();
68
+
69
+ // Clamp value between 0 and 100
70
+ const clampedValue = Math.max(0, Math.min(100, value));
71
+
72
+ // Default colors
73
+ const progressColor = color || tokens.colors.primary;
74
+ const progressBackground = backgroundColor || tokens.colors.surfaceVariant;
75
+ const progressTextColor = textColor || tokens.colors.textPrimary;
76
+
77
+ // Calculate progress width
78
+ const progressWidth = `${clampedValue}%`;
79
+
80
+ // Border radius based on shape
81
+ const borderRadius = shape === 'rounded' ? height / 2 : 0;
82
+
83
+ const containerStyle: ViewStyle = {
84
+ width: width as DimensionValue,
85
+ height,
86
+ backgroundColor: progressBackground,
87
+ borderRadius,
88
+ overflow: 'hidden',
89
+ };
90
+
91
+ const progressStyle: ViewStyle = {
92
+ width: progressWidth as DimensionValue,
93
+ height: '100%' as DimensionValue,
94
+ backgroundColor: progressColor,
95
+ borderRadius,
96
+ };
97
+
98
+ const textStyle = {
99
+ fontSize: tokens.typography.bodySmall.fontSize,
100
+ fontWeight: tokens.typography.labelMedium.fontWeight,
101
+ color: progressTextColor,
102
+ textAlign: 'center' as const,
103
+ };
104
+
105
+ return (
106
+ <View
107
+ style={[containerStyle, style]}
108
+ testID={testID}
109
+ accessibilityRole="progressbar"
110
+ accessibilityValue={{ min: 0, max: 100, now: Math.round(clampedValue) }}
111
+ accessibilityLabel={`Progress: ${Math.round(clampedValue)}${showPercentage ? '%' : ''}`}
112
+ >
113
+ <View style={progressStyle} />
114
+ {(showPercentage || showValue) && (
115
+ <View style={styles.textContainer}>
116
+ <Text
117
+ style={textStyle}
118
+ accessibilityLiveRegion="polite"
119
+ accessibilityLabel={`Current progress: ${Math.round(clampedValue)}${showPercentage ? '%' : ''}`}
120
+ >
121
+ {showPercentage ? `${Math.round(clampedValue)}%` : `${Math.round(clampedValue)}`}
122
+ </Text>
123
+ </View>
124
+ )}
125
+ </View>
126
+ );
127
+ };
128
+
129
+ // =============================================================================
130
+ // STYLES
131
+ // =============================================================================
132
+
133
+ const styles = StyleSheet.create({
134
+ textContainer: {
135
+ position: 'absolute',
136
+ top: 0,
137
+ left: 0,
138
+ right: 0,
139
+ bottom: 0,
140
+ justifyContent: 'center',
141
+ alignItems: 'center',
142
+ },
143
+ });
144
+
145
+ // =============================================================================
146
+ // EXPORTS
147
+ // =============================================================================
148
+
149
+ export default AtomicProgress;
@@ -0,0 +1,55 @@
1
+ import React from 'react';
2
+ import { Text, StyleProp, TextStyle } from 'react-native';
3
+ import { useAppDesignTokens } from '../theme';
4
+ import type { TextStyleVariant, ColorVariant } from '../typography';
5
+ import { getTextColor } from '../typography';
6
+
7
+ export interface AtomicTextProps {
8
+ children: React.ReactNode;
9
+ type?: TextStyleVariant;
10
+ color?: ColorVariant | string;
11
+ numberOfLines?: number;
12
+ ellipsizeMode?: 'head' | 'middle' | 'tail' | 'clip';
13
+ textAlign?: 'auto' | 'left' | 'right' | 'center' | 'justify';
14
+ style?: StyleProp<TextStyle>;
15
+ testID?: string;
16
+ }
17
+
18
+ export const AtomicText: React.FC<AtomicTextProps> = ({
19
+ children,
20
+ type = 'bodyMedium',
21
+ color,
22
+ numberOfLines,
23
+ ellipsizeMode,
24
+ textAlign,
25
+ style,
26
+ testID,
27
+ }) => {
28
+ const tokens = useAppDesignTokens();
29
+
30
+ // Get typography style from tokens
31
+ const typographyStyle = (tokens.typography as Record<string, any>)[type];
32
+
33
+ // Get color from tokens or use custom color using utility function
34
+ const resolvedColor = getTextColor(color, tokens);
35
+
36
+ const textStyle: StyleProp<TextStyle> = [
37
+ typographyStyle,
38
+ {
39
+ color: resolvedColor,
40
+ ...(textAlign && { textAlign }),
41
+ },
42
+ style,
43
+ ];
44
+
45
+ return (
46
+ <Text
47
+ numberOfLines={numberOfLines}
48
+ ellipsizeMode={ellipsizeMode}
49
+ style={textStyle}
50
+ testID={testID}
51
+ >
52
+ {children}
53
+ </Text>
54
+ );
55
+ };
@@ -0,0 +1,107 @@
1
+ /**
2
+ * AtomicButton Tests
3
+ *
4
+ * Basic test cases for AtomicButton component
5
+ * These tests can be run by consuming applications
6
+ */
7
+
8
+ import React from 'react';
9
+ import { render, fireEvent } from '@testing-library/react-native';
10
+ import { AtomicButton } from '../AtomicButton';
11
+
12
+ // Mock design tokens
13
+ jest.mock('@umituz/react-native-design-system-theme', () => ({
14
+ useAppDesignTokens: () => ({
15
+ colors: {
16
+ primary: '#007AFF',
17
+ onPrimary: '#FFFFFF',
18
+ secondary: '#8E8E93',
19
+ surface: '#F2F2F7',
20
+ border: '#C6C6C8',
21
+ },
22
+ spacing: {
23
+ xs: 4,
24
+ sm: 8,
25
+ md: 16,
26
+ lg: 24,
27
+ },
28
+ typography: {
29
+ bodyMedium: { fontSize: 16 },
30
+ labelLarge: { fontSize: 18 },
31
+ },
32
+ borders: {
33
+ radius: {
34
+ md: 8,
35
+ },
36
+ },
37
+ }),
38
+ }));
39
+
40
+ describe('AtomicButton', () => {
41
+ it('renders correctly with title', () => {
42
+ const { getByText } = render(
43
+ <AtomicButton title="Test Button" onPress={() => {}} />
44
+ );
45
+
46
+ expect(getByText('Test Button')).toBeTruthy();
47
+ });
48
+
49
+ it('handles press events', () => {
50
+ const mockOnPress = jest.fn();
51
+ const { getByText } = render(
52
+ <AtomicButton title="Test Button" onPress={mockOnPress} />
53
+ );
54
+
55
+ fireEvent.press(getByText('Test Button'));
56
+ expect(mockOnPress).toHaveBeenCalledTimes(1);
57
+ });
58
+
59
+ it('renders with different variants', () => {
60
+ const { getByText, rerender } = render(
61
+ <AtomicButton title="Primary" onPress={() => {}} variant="primary" />
62
+ );
63
+
64
+ expect(getByText('Primary')).toBeTruthy();
65
+
66
+ rerender(<AtomicButton title="Secondary" onPress={() => {}} variant="secondary" />);
67
+ expect(getByText('Secondary')).toBeTruthy();
68
+ });
69
+
70
+ it('renders with different sizes', () => {
71
+ const { getByText, rerender } = render(
72
+ <AtomicButton title="Small" onPress={() => {}} size="sm" />
73
+ );
74
+
75
+ expect(getByText('Small')).toBeTruthy();
76
+
77
+ rerender(<AtomicButton title="Large" onPress={() => {}} size="lg" />);
78
+ expect(getByText('Large')).toBeTruthy();
79
+ });
80
+
81
+ it('is disabled when disabled prop is true', () => {
82
+ const mockOnPress = jest.fn();
83
+ const { getByText } = render(
84
+ <AtomicButton title="Disabled" onPress={mockOnPress} disabled />
85
+ );
86
+
87
+ const button = getByText('Disabled').parent;
88
+ expect(button).toBeDisabled();
89
+ });
90
+
91
+ it('renders with icon', () => {
92
+ const { getByTestId } = render(
93
+ <AtomicButton title="With Icon" onPress={() => {}} icon="Settings" testID="test-button" />
94
+ );
95
+
96
+ expect(getByTestId('test-button')).toBeTruthy();
97
+ });
98
+
99
+ it('applies custom styles', () => {
100
+ const customStyle = { backgroundColor: 'red' };
101
+ const { getByTestId } = render(
102
+ <AtomicButton title="Custom" onPress={() => {}} style={customStyle} testID="test-button" />
103
+ );
104
+
105
+ expect(getByTestId('test-button')).toBeTruthy();
106
+ });
107
+ });