@umituz/react-native-design-system 2.6.61 → 2.6.64

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 (68) hide show
  1. package/package.json +1 -1
  2. package/src/atoms/AtomicButton.tsx +6 -257
  3. package/src/atoms/AtomicChip.tsx +4 -224
  4. package/src/atoms/AtomicIcon.tsx +2 -6
  5. package/src/atoms/AtomicIcon.types.ts +5 -0
  6. package/src/atoms/AtomicInput.tsx +34 -154
  7. package/src/atoms/AtomicPicker.tsx +31 -123
  8. package/src/atoms/button/AtomicButton.tsx +108 -0
  9. package/src/atoms/button/configs/buttonSizeConfig.ts +37 -0
  10. package/src/atoms/button/index.ts +6 -0
  11. package/src/atoms/button/styles/buttonStyles.ts +36 -0
  12. package/src/atoms/button/styles/buttonVariantStyles.ts +88 -0
  13. package/src/atoms/button/types/index.ts +40 -0
  14. package/src/atoms/chip/AtomicChip.tsx +112 -0
  15. package/src/atoms/chip/configs/chipColorConfig.ts +47 -0
  16. package/src/atoms/chip/configs/chipSizeConfig.ts +34 -0
  17. package/src/atoms/chip/index.ts +6 -0
  18. package/src/atoms/chip/styles/chipStyles.ts +28 -0
  19. package/src/atoms/chip/types/index.ts +42 -0
  20. package/src/atoms/index.ts +6 -4
  21. package/src/atoms/input/components/InputHelper.tsx +49 -0
  22. package/src/atoms/input/components/InputIcon.tsx +44 -0
  23. package/src/atoms/input/components/InputLabel.tsx +20 -0
  24. package/src/atoms/input/styles/inputStylesHelper.ts +1 -1
  25. package/src/atoms/input/types.ts +72 -0
  26. package/src/atoms/picker/hooks/usePickerState.ts +139 -0
  27. package/src/exports/atoms.ts +69 -0
  28. package/src/exports/device.ts +58 -0
  29. package/src/exports/layouts.ts +19 -0
  30. package/src/exports/molecules.ts +166 -0
  31. package/src/exports/organisms.ts +9 -0
  32. package/src/exports/responsive.ts +36 -0
  33. package/src/exports/safe-area.ts +6 -0
  34. package/src/exports/theme.ts +47 -0
  35. package/src/exports/typography.ts +22 -0
  36. package/src/exports/utilities.ts +6 -0
  37. package/src/exports/variants.ts +22 -0
  38. package/src/index.ts +11 -417
  39. package/src/layouts/ScreenLayout/ScreenLayout.tsx +17 -181
  40. package/src/layouts/ScreenLayout/components/ContentWrapper.tsx +31 -0
  41. package/src/layouts/ScreenLayout/components/index.ts +6 -0
  42. package/src/layouts/ScreenLayout/styles/screenLayoutStyles.ts +47 -0
  43. package/src/layouts/ScreenLayout/types/index.ts +27 -0
  44. package/src/molecules/avatar/Avatar.constants.ts +103 -0
  45. package/src/molecules/avatar/Avatar.types.ts +64 -0
  46. package/src/molecules/avatar/Avatar.utils.ts +8 -160
  47. package/src/molecules/calendar/index.ts +4 -9
  48. package/src/molecules/calendar/infrastructure/storage/CalendarStore.ts +103 -302
  49. package/src/molecules/calendar/infrastructure/storage/CalendarStore.ts.bak +116 -0
  50. package/src/molecules/calendar/infrastructure/storage/CalendarStore.types.ts +64 -0
  51. package/src/molecules/calendar/infrastructure/storage/CalendarStore.utils.ts +56 -0
  52. package/src/molecules/calendar/infrastructure/storage/EventActions.ts +140 -0
  53. package/src/molecules/calendar/infrastructure/storage/NavigationActions.ts +118 -0
  54. package/src/molecules/calendar/infrastructure/stores/storageAdapter.ts +34 -0
  55. package/src/molecules/calendar/infrastructure/stores/useCalendarEvents.ts +168 -0
  56. package/src/molecules/calendar/infrastructure/stores/useCalendarNavigation.ts +47 -0
  57. package/src/molecules/calendar/infrastructure/stores/useCalendarView.ts +24 -0
  58. package/src/molecules/calendar/presentation/hooks/useCalendar.ts +7 -11
  59. package/src/responsive/compute/computeDeviceInfo.ts +22 -0
  60. package/src/responsive/compute/computeResponsivePositioning.ts +42 -0
  61. package/src/responsive/compute/computeResponsiveSizes.ts +48 -0
  62. package/src/responsive/padding/paddingUtils.ts +65 -0
  63. package/src/responsive/positioning/positioningUtils.ts +61 -0
  64. package/src/responsive/responsiveLayout.ts +11 -264
  65. package/src/responsive/screen/screenLayoutConfig.ts +38 -0
  66. package/src/responsive/tabbar/tabBarConfig.ts +88 -0
  67. package/src/responsive/types/responsiveTypes.ts +69 -0
  68. package/src/responsive/useResponsive.ts +69 -158
@@ -1,78 +1,12 @@
1
1
  import React from 'react';
2
- import { View, TextInput, Pressable, StyleSheet, StyleProp, ViewStyle, TextStyle } from 'react-native';
2
+ import { View, TextInput, StyleSheet, StyleProp, ViewStyle, TextStyle } from 'react-native';
3
3
  import { useAppDesignTokens } from '../theme';
4
- import { AtomicIcon } from './AtomicIcon';
5
- import { AtomicText } from './AtomicText';
6
4
  import { useInputState } from './input/hooks/useInputState';
7
5
  import { getSizeConfig, getVariantStyle, getTextColor } from './input/styles/inputStylesHelper';
8
- import type { IconName } from './AtomicIcon';
9
-
10
- export type AtomicInputVariant = 'outlined' | 'filled' | 'flat';
11
- export type AtomicInputState = 'default' | 'error' | 'success' | 'disabled';
12
- export type AtomicInputSize = 'sm' | 'md' | 'lg';
13
-
14
- export interface AtomicInputProps {
15
- /** Input label */
16
- label?: string;
17
- /** Current input value */
18
- value?: string;
19
- /** Value change callback */
20
- onChangeText?: (text: string) => void;
21
- /** Input variant (outlined, filled, flat) */
22
- variant?: AtomicInputVariant;
23
- /** Input state (default, error, success, disabled) */
24
- state?: AtomicInputState;
25
- /** Input size (sm, md, lg) */
26
- size?: AtomicInputSize;
27
- /** Placeholder text */
28
- placeholder?: string;
29
- /** Helper text below input */
30
- helperText?: string;
31
- /** Leading icon (Ionicons name) */
32
- leadingIcon?: IconName;
33
- /** Trailing icon (Ionicons name) */
34
- trailingIcon?: IconName;
35
- /** Callback when trailing icon is pressed */
36
- onTrailingIconPress?: () => void;
37
- /** Show password toggle for secure inputs */
38
- showPasswordToggle?: boolean;
39
- /** Secure text entry (password field) */
40
- secureTextEntry?: boolean;
41
- /** Maximum character length */
42
- maxLength?: number;
43
- /** Show character counter */
44
- showCharacterCount?: boolean;
45
- /** Keyboard type */
46
- keyboardType?: 'default' | 'email-address' | 'numeric' | 'phone-pad' | 'url' | 'number-pad' | 'decimal-pad' | 'web-search' | 'twitter' | 'numeric' | 'visible-password';
47
- /** Return key type */
48
- returnKeyType?: 'done' | 'go' | 'next' | 'search' | 'send';
49
- /** Callback when submit button is pressed */
50
- onSubmitEditing?: () => void;
51
- /** Blur on submit */
52
- blurOnSubmit?: boolean;
53
- /** Auto focus */
54
- autoFocus?: boolean;
55
- /** Auto-capitalize */
56
- autoCapitalize?: 'none' | 'sentences' | 'words' | 'characters';
57
- /** Auto-correct */
58
- autoCorrect?: boolean;
59
- /** Disabled state */
60
- disabled?: boolean;
61
- /** Container style */
62
- style?: StyleProp<ViewStyle>;
63
- /** Input text style */
64
- inputStyle?: StyleProp<TextStyle>;
65
- /** Test ID for E2E testing */
66
- testID?: string;
67
- /** Blur callback */
68
- onBlur?: () => void;
69
- /** Focus callback */
70
- onFocus?: () => void;
71
- /** Multiline input support */
72
- multiline?: boolean;
73
- /** Number of lines for multiline input */
74
- numberOfLines?: number;
75
- }
6
+ import type { AtomicInputProps } from './input/types';
7
+ import { InputLabel } from './input/components/InputLabel';
8
+ import { InputIcon } from './input/components/InputIcon';
9
+ import { InputHelper } from './input/components/InputHelper';
76
10
 
77
11
  /**
78
12
  * AtomicInput - Pure React Native Text Input
@@ -188,25 +122,16 @@ export const AtomicInput = React.forwardRef<TextInput, AtomicInputProps>(({
188
122
 
189
123
  return (
190
124
  <View testID={testID}>
191
- {label && (
192
- <AtomicText
193
- type="labelMedium"
194
- color={hasError ? 'error' : hasSuccess ? 'success' : 'secondary'}
195
- style={styles.label}
196
- >
197
- {label}
198
- </AtomicText>
199
- )}
125
+ <InputLabel label={label} state={state} />
200
126
 
201
127
  <View style={containerStyle}>
202
128
  {leadingIcon && (
203
- <View style={styles.leadingIcon}>
204
- <AtomicIcon
205
- name={leadingIcon}
206
- customSize={sizeConfig.iconSize}
207
- customColor={iconColor}
208
- />
209
- </View>
129
+ <InputIcon
130
+ name={leadingIcon}
131
+ size={sizeConfig.iconSize}
132
+ color={iconColor}
133
+ position="leading"
134
+ />
210
135
  )}
211
136
 
212
137
  <TextInput
@@ -240,55 +165,36 @@ export const AtomicInput = React.forwardRef<TextInput, AtomicInputProps>(({
240
165
  />
241
166
 
242
167
  {(showPasswordToggle && secureTextEntry) && (
243
- <Pressable
168
+ <InputIcon
169
+ name={isPasswordVisible ? "eye-off-outline" : "eye-outline"}
170
+ size={sizeConfig.iconSize}
171
+ color={iconColor}
172
+ position="trailing"
244
173
  onPress={() => togglePasswordVisibility()}
245
- style={styles.trailingIcon}
246
- >
247
- <AtomicIcon
248
- name={isPasswordVisible ? "eye-off-outline" : "eye-outline"}
249
- customSize={sizeConfig.iconSize}
250
- customColor={iconColor}
251
- />
252
- </Pressable>
174
+ testID={testID ? `${testID}-toggle-password` : undefined}
175
+ />
253
176
  )}
254
177
 
255
178
  {trailingIcon && !showPasswordToggle && (
256
- <Pressable
179
+ <InputIcon
180
+ name={trailingIcon}
181
+ size={sizeConfig.iconSize}
182
+ color={iconColor}
183
+ position="trailing"
257
184
  onPress={onTrailingIconPress}
258
- style={styles.trailingIcon}
259
- disabled={!onTrailingIconPress}
260
- >
261
- <AtomicIcon
262
- name={trailingIcon}
263
- customSize={sizeConfig.iconSize}
264
- customColor={iconColor}
265
- />
266
- </Pressable>
185
+ testID={testID ? `${testID}-trailing-icon` : undefined}
186
+ />
267
187
  )}
268
188
  </View>
269
189
 
270
- {(helperText || showCharacterCount) && (
271
- <View style={styles.helperRow}>
272
- {helperText && (
273
- <AtomicText
274
- style={styles.helperText}
275
- color={hasError ? 'error' : 'secondary'}
276
- testID={testID ? `${testID}-helper` : undefined}
277
- >
278
- {helperText}
279
- </AtomicText>
280
- )}
281
- {showCharacterCount && maxLength && (
282
- <AtomicText
283
- style={[styles.helperText, styles.characterCount]}
284
- color="secondary"
285
- testID={testID ? `${testID}-count` : undefined}
286
- >
287
- {characterCount}/{maxLength}
288
- </AtomicText>
289
- )}
290
- </View>
291
- )}
190
+ <InputHelper
191
+ helperText={helperText}
192
+ showCharacterCount={showCharacterCount}
193
+ characterCount={characterCount}
194
+ maxLength={maxLength}
195
+ state={state}
196
+ testID={testID}
197
+ />
292
198
  </View>
293
199
  );
294
200
  });
@@ -303,30 +209,4 @@ const styles = StyleSheet.create({
303
209
  margin: 0,
304
210
  padding: 0,
305
211
  },
306
- label: {
307
- marginBottom: 4,
308
- },
309
- leadingIcon: {
310
- position: 'absolute',
311
- left: 12,
312
- zIndex: 1,
313
- },
314
- trailingIcon: {
315
- position: 'absolute',
316
- right: 12,
317
- zIndex: 1,
318
- },
319
- helperRow: {
320
- flexDirection: 'row',
321
- justifyContent: 'space-between',
322
- marginTop: 4,
323
- },
324
- helperText: {
325
- flex: 1,
326
- },
327
- characterCount: {
328
- marginLeft: 8,
329
- },
330
212
  });
331
-
332
- export type { AtomicInputProps as InputProps };
@@ -40,7 +40,7 @@
40
40
  * @module AtomicPicker
41
41
  */
42
42
 
43
- import React, { useState, useMemo } from 'react';
43
+ import React from 'react';
44
44
  import {
45
45
  View,
46
46
  TouchableOpacity,
@@ -59,6 +59,7 @@ import {
59
59
  getPickerValueStyles,
60
60
  getPickerErrorStyles,
61
61
  } from './picker/styles/pickerStyles';
62
+ import { usePickerState } from './picker/hooks/usePickerState';
62
63
 
63
64
  export type { AtomicPickerProps, PickerOption, PickerSize } from './picker/types';
64
65
 
@@ -92,8 +93,14 @@ export const AtomicPicker: React.FC<AtomicPickerProps> = ({
92
93
  }) => {
93
94
  const tokens = useAppDesignTokens();
94
95
 
95
- const [modalVisible, setModalVisible] = useState(false);
96
- const [searchQuery, setSearchQuery] = useState('');
96
+ const pickerState = usePickerState({
97
+ value,
98
+ multiple,
99
+ options,
100
+ placeholder,
101
+ autoClose,
102
+ onChange,
103
+ });
97
104
 
98
105
  // Get style helpers with design tokens
99
106
  const containerStyles = getPickerContainerStyles(tokens);
@@ -102,110 +109,6 @@ export const AtomicPicker: React.FC<AtomicPickerProps> = ({
102
109
  const valueStyles = getPickerValueStyles(tokens);
103
110
  const errorStyles = getPickerErrorStyles(tokens);
104
111
 
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 || placeholder
147
- : `${selectedOptions.length} selected`;
148
- }
149
- return selectedOptions[0]?.label || placeholder;
150
- }, [selectedOptions, placeholder, multiple]);
151
-
152
- /**
153
- * Handle modal open
154
- */
155
- const openModal = () => {
156
- if (disabled) return;
157
- setModalVisible(true);
158
- setSearchQuery('');
159
- };
160
-
161
- /**
162
- * Handle modal close
163
- */
164
- const closeModal = () => {
165
- setModalVisible(false);
166
- setSearchQuery('');
167
- };
168
-
169
- /**
170
- * Handle option selection
171
- */
172
- const handleSelect = (optionValue: string) => {
173
- if (multiple) {
174
- const newValues = selectedValues.includes(optionValue)
175
- ? selectedValues.filter((v) => v !== optionValue)
176
- : [...selectedValues, optionValue];
177
- onChange(newValues);
178
- } else {
179
- onChange(optionValue);
180
- if (autoClose) {
181
- closeModal();
182
- }
183
- }
184
- };
185
-
186
- /**
187
- * Handle clear selection
188
- */
189
- const handleClear = () => {
190
- onChange(multiple ? [] : '');
191
- };
192
-
193
- /**
194
- * Handle search query change
195
- */
196
- const handleSearch = (query: string) => {
197
- setSearchQuery(query);
198
- };
199
-
200
-
201
-
202
- /**
203
- * Handle chip removal
204
- */
205
- const handleChipRemove = (value: string) => {
206
- handleSelect(value);
207
- };
208
-
209
112
  const pickerContainerStyle = StyleSheet.flatten([
210
113
  containerStyles.base,
211
114
  containerStyles.size[size],
@@ -221,12 +124,17 @@ export const AtomicPicker: React.FC<AtomicPickerProps> = ({
221
124
  ]);
222
125
 
223
126
  const pickerValueStyle = StyleSheet.flatten([
224
- selectedOptions.length > 0 ? valueStyles.base : placeholderStyles.base,
225
- selectedOptions.length > 0
127
+ pickerState.selectedOptions.length > 0 ? valueStyles.base : placeholderStyles.base,
128
+ pickerState.selectedOptions.length > 0
226
129
  ? valueStyles.size[size]
227
130
  : placeholderStyles.size[size],
228
131
  ]);
229
132
 
133
+ const handleOpenModal = () => {
134
+ if (disabled) return;
135
+ pickerState.openModal();
136
+ };
137
+
230
138
  return (
231
139
  <View>
232
140
  {/* Label */}
@@ -234,7 +142,7 @@ export const AtomicPicker: React.FC<AtomicPickerProps> = ({
234
142
 
235
143
  {/* Picker Button */}
236
144
  <TouchableOpacity
237
- onPress={openModal}
145
+ onPress={handleOpenModal}
238
146
  disabled={disabled}
239
147
  accessibilityRole="button"
240
148
  accessibilityLabel={label || placeholder}
@@ -244,15 +152,15 @@ export const AtomicPicker: React.FC<AtomicPickerProps> = ({
244
152
  >
245
153
  {/* Display Text */}
246
154
  <AtomicText style={pickerValueStyle} numberOfLines={1}>
247
- {displayText}
155
+ {pickerState.displayText}
248
156
  </AtomicText>
249
157
 
250
158
  {/* Icons */}
251
159
  <View style={{ flexDirection: 'row', alignItems: 'center', gap: tokens.spacing.xs }}>
252
160
  {/* Clear Button */}
253
- {clearable && selectedOptions.length > 0 && !disabled && (
161
+ {clearable && pickerState.selectedOptions.length > 0 && !disabled && (
254
162
  <TouchableOpacity
255
- onPress={handleClear}
163
+ onPress={pickerState.handleClear}
256
164
  hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }}
257
165
  accessibilityRole="button"
258
166
  accessibilityLabel={clearAccessibilityLabel}
@@ -264,7 +172,7 @@ export const AtomicPicker: React.FC<AtomicPickerProps> = ({
264
172
 
265
173
  {/* Dropdown Icon */}
266
174
  <AtomicIcon
267
- name={modalVisible ? 'ChevronUp' : 'ChevronDown'}
175
+ name={pickerState.modalVisible ? 'ChevronUp' : 'ChevronDown'}
268
176
  size="sm"
269
177
  color={disabled ? 'surfaceVariant' : 'secondary'}
270
178
  />
@@ -273,8 +181,8 @@ export const AtomicPicker: React.FC<AtomicPickerProps> = ({
273
181
 
274
182
  {/* Selected Chips (Multi-select) */}
275
183
  <PickerChips
276
- selectedOptions={selectedOptions}
277
- onRemoveChip={handleChipRemove}
184
+ selectedOptions={pickerState.selectedOptions}
185
+ onRemoveChip={pickerState.handleChipRemove}
278
186
  testID={testID}
279
187
  />
280
188
 
@@ -283,16 +191,16 @@ export const AtomicPicker: React.FC<AtomicPickerProps> = ({
283
191
 
284
192
  {/* Selection Modal */}
285
193
  <PickerModal
286
- visible={modalVisible}
287
- onClose={closeModal}
194
+ visible={pickerState.modalVisible}
195
+ onClose={pickerState.closeModal}
288
196
  options={options}
289
- selectedValues={selectedValues}
290
- onSelect={handleSelect}
197
+ selectedValues={pickerState.selectedValues}
198
+ onSelect={pickerState.handleSelect}
291
199
  title={modalTitle || label}
292
200
  searchable={searchable}
293
- searchQuery={searchQuery}
294
- onSearchChange={handleSearch}
295
- filteredOptions={filteredOptions}
201
+ searchQuery={pickerState.searchQuery}
202
+ onSearchChange={pickerState.handleSearch}
203
+ filteredOptions={pickerState.filteredOptions}
296
204
  multiple={multiple}
297
205
  emptyMessage={emptyMessage}
298
206
  searchPlaceholder={searchPlaceholder}
@@ -0,0 +1,108 @@
1
+ /**
2
+ * AtomicButton Component
3
+ * Refactored: Extracted configs, styles, and types
4
+ */
5
+
6
+ import React from 'react';
7
+ import { StyleProp, ViewStyle, TextStyle, TouchableOpacity, View } from 'react-native';
8
+ import { AtomicText } from '../AtomicText';
9
+ import { AtomicIcon } from '../AtomicIcon';
10
+ import { AtomicSpinner } from '../AtomicSpinner';
11
+ import { useAppDesignTokens } from '../../theme';
12
+ import { getButtonSizeConfig } from './configs/buttonSizeConfig';
13
+ import { getVariantStyles } from './styles/buttonVariantStyles';
14
+ import { buttonStyles } from './styles/buttonStyles';
15
+ import type { AtomicButtonProps } from './types';
16
+
17
+ export const AtomicButton: React.FC<AtomicButtonProps> = React.memo(({
18
+ title,
19
+ children,
20
+ onPress,
21
+ variant = 'primary',
22
+ size = 'md',
23
+ disabled = false,
24
+ loading = false,
25
+ icon,
26
+ iconPosition = 'left',
27
+ fullWidth = false,
28
+ style,
29
+ textStyle,
30
+ activeOpacity = 0.8,
31
+ testID,
32
+ }) => {
33
+ const tokens = useAppDesignTokens();
34
+
35
+ const handlePress = () => {
36
+ if (!disabled && !loading) {
37
+ onPress();
38
+ }
39
+ };
40
+
41
+ const isDisabled = disabled || loading;
42
+ const config = getButtonSizeConfig(size, tokens);
43
+ const variantStyles = getVariantStyles(variant, tokens);
44
+ const iconColor = variantStyles.text.color;
45
+
46
+ const containerStyle: StyleProp<ViewStyle> = [
47
+ buttonStyles.button,
48
+ {
49
+ paddingVertical: config.paddingVertical,
50
+ paddingHorizontal: config.paddingHorizontal,
51
+ minHeight: config.minHeight,
52
+ borderRadius: tokens.borders.radius.md,
53
+ },
54
+ variantStyles.container,
55
+ fullWidth ? buttonStyles.fullWidth : undefined,
56
+ isDisabled ? buttonStyles.disabled : undefined,
57
+ style,
58
+ ];
59
+
60
+ const buttonTextStyle: StyleProp<TextStyle> = [
61
+ {
62
+ fontSize: config.fontSize,
63
+ fontWeight: '600',
64
+ },
65
+ variantStyles.text,
66
+ isDisabled ? buttonStyles.disabledText : undefined,
67
+ textStyle,
68
+ ];
69
+
70
+ const buttonText = title || children;
71
+ const showIcon = icon;
72
+
73
+ return (
74
+ <TouchableOpacity
75
+ style={containerStyle}
76
+ onPress={handlePress}
77
+ activeOpacity={activeOpacity}
78
+ disabled={isDisabled}
79
+ testID={testID}
80
+ >
81
+ <View style={[buttonStyles.content, iconPosition === 'right' && buttonStyles.rowReverse]}>
82
+ {loading ? (
83
+ <AtomicSpinner
84
+ size="sm"
85
+ color={iconColor as string}
86
+ style={iconPosition === 'right' ? buttonStyles.iconRight : buttonStyles.iconLeft}
87
+ />
88
+ ) : showIcon ? (
89
+ <AtomicIcon
90
+ name={icon}
91
+ customSize={config.iconSize}
92
+ customColor={iconColor as string | undefined}
93
+ style={iconPosition === 'right' ? buttonStyles.iconRight : buttonStyles.iconLeft}
94
+ />
95
+ ) : null}
96
+
97
+ <AtomicText style={buttonTextStyle}>
98
+ {buttonText}
99
+ </AtomicText>
100
+ </View>
101
+ </TouchableOpacity>
102
+ );
103
+ });
104
+
105
+ AtomicButton.displayName = 'AtomicButton';
106
+
107
+ // Re-export types for convenience
108
+ export type { AtomicButtonProps, ButtonVariant, ButtonSize } from './types';
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Button Size Configuration
3
+ */
4
+
5
+ import type { DesignTokens } from '../../../theme';
6
+ import type { ButtonSize, ButtonSizeConfig } from '../types';
7
+
8
+ export const getButtonSizeConfig = (
9
+ size: ButtonSize,
10
+ tokens: DesignTokens,
11
+ ): ButtonSizeConfig => {
12
+ const sizeConfigs: Record<ButtonSize, ButtonSizeConfig> = {
13
+ sm: {
14
+ paddingVertical: tokens.spacing.xs,
15
+ paddingHorizontal: tokens.spacing.sm,
16
+ fontSize: tokens.typography.bodySmall.responsiveFontSize,
17
+ iconSize: 16 * tokens.spacingMultiplier,
18
+ minHeight: 32 * tokens.spacingMultiplier,
19
+ },
20
+ md: {
21
+ paddingVertical: tokens.spacing.sm,
22
+ paddingHorizontal: tokens.spacing.md,
23
+ fontSize: tokens.typography.bodyMedium.responsiveFontSize,
24
+ iconSize: 20 * tokens.spacingMultiplier,
25
+ minHeight: 44 * tokens.spacingMultiplier,
26
+ },
27
+ lg: {
28
+ paddingVertical: tokens.spacing.md,
29
+ paddingHorizontal: tokens.spacing.lg,
30
+ fontSize: tokens.typography.bodyLarge.responsiveFontSize,
31
+ iconSize: 24 * tokens.spacingMultiplier,
32
+ minHeight: 52 * tokens.spacingMultiplier,
33
+ },
34
+ };
35
+
36
+ return sizeConfigs[size];
37
+ };
@@ -0,0 +1,6 @@
1
+ /**
2
+ * AtomicButton Barrel Export
3
+ */
4
+
5
+ export { AtomicButton } from './AtomicButton';
6
+ export type { AtomicButtonProps, ButtonVariant, ButtonSize } from './types';
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Button Base Styles
3
+ */
4
+
5
+ import { StyleSheet } from 'react-native';
6
+
7
+ export const buttonStyles = StyleSheet.create({
8
+ button: {
9
+ alignItems: 'center',
10
+ justifyContent: 'center',
11
+ flexDirection: 'row',
12
+ },
13
+ content: {
14
+ flexDirection: 'row',
15
+ alignItems: 'center',
16
+ justifyContent: 'center',
17
+ },
18
+ rowReverse: {
19
+ flexDirection: 'row-reverse',
20
+ },
21
+ fullWidth: {
22
+ width: '100%',
23
+ },
24
+ disabled: {
25
+ opacity: 0.5,
26
+ },
27
+ disabledText: {
28
+ opacity: 0.7,
29
+ },
30
+ iconLeft: {
31
+ marginRight: 8,
32
+ },
33
+ iconRight: {
34
+ marginLeft: 8,
35
+ },
36
+ });