@umituz/react-native-design-system 1.14.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 +148 -79
  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,226 @@
1
+ /**
2
+ * AtomicChip - Universal Chip/Tag Component
3
+ *
4
+ * Displays small tags, labels, or status indicators
5
+ * Theme: {{THEME_NAME}} ({{CATEGORY}} category)
6
+ *
7
+ * Atomic Design Level: ATOM
8
+ * Purpose: Tag and label display
9
+ *
10
+ * Usage:
11
+ * - Category tags
12
+ * - Status indicators
13
+ * - Filter chips
14
+ * - Skill labels
15
+ * - Badge displays
16
+ */
17
+
18
+ import React from 'react';
19
+ import { View, StyleSheet, ViewStyle, TouchableOpacity } from 'react-native';
20
+ import { AtomicText } from './AtomicText';
21
+ import { AtomicIcon } from './AtomicIcon';
22
+ import { useAppDesignTokens } from '../theme';
23
+
24
+ // =============================================================================
25
+ // TYPE DEFINITIONS
26
+ // =============================================================================
27
+
28
+ export interface AtomicChipProps {
29
+ /** Text content of the chip */
30
+ children: React.ReactNode;
31
+ /** Chip variant */
32
+ variant?: 'filled' | 'outlined' | 'soft';
33
+ /** Chip size */
34
+ size?: 'sm' | 'md' | 'lg';
35
+ /** Chip color theme */
36
+ color?: 'primary' | 'secondary' | 'success' | 'warning' | 'error' | 'info';
37
+ /** Custom background color */
38
+ backgroundColor?: string;
39
+ /** Custom text color */
40
+ textColor?: string;
41
+ /** Custom border color */
42
+ borderColor?: string;
43
+ /** Leading icon */
44
+ leadingIcon?: string;
45
+ /** Trailing icon */
46
+ trailingIcon?: string;
47
+ /** Whether the chip is clickable */
48
+ clickable?: boolean;
49
+ /** Click handler */
50
+ onPress?: () => void;
51
+ /** Whether the chip is selected */
52
+ selected?: boolean;
53
+ /** Whether the chip is disabled */
54
+ disabled?: boolean;
55
+ /** Style overrides */
56
+ style?: ViewStyle;
57
+ /** Test ID for testing */
58
+ testID?: string;
59
+ /** Active opacity for touch feedback */
60
+ activeOpacity?: number;
61
+ }
62
+
63
+ // =============================================================================
64
+ // COMPONENT IMPLEMENTATION
65
+ // =============================================================================
66
+
67
+ export const AtomicChip: React.FC<AtomicChipProps> = React.memo(({
68
+ children,
69
+ variant = 'filled',
70
+ size = 'md',
71
+ color = 'primary',
72
+ backgroundColor,
73
+ textColor,
74
+ borderColor,
75
+ leadingIcon,
76
+ trailingIcon,
77
+ clickable = false,
78
+ onPress,
79
+ selected = false,
80
+ disabled = false,
81
+ style,
82
+ testID,
83
+ activeOpacity = 0.7,
84
+ }) => {
85
+ const tokens = useAppDesignTokens();
86
+
87
+ // Size mapping
88
+ const sizeMap = {
89
+ sm: {
90
+ paddingHorizontal: tokens.spacing.sm,
91
+ paddingVertical: tokens.spacing.xs,
92
+ fontSize: tokens.typography.bodySmall.fontSize,
93
+ iconSize: 'xs' as const
94
+ },
95
+ md: {
96
+ paddingHorizontal: tokens.spacing.md,
97
+ paddingVertical: tokens.spacing.sm,
98
+ fontSize: tokens.typography.bodyMedium.fontSize,
99
+ iconSize: 'sm' as const
100
+ },
101
+ lg: {
102
+ paddingHorizontal: tokens.spacing.md,
103
+ paddingVertical: tokens.spacing.sm,
104
+ fontSize: tokens.typography.bodyLarge.fontSize,
105
+ iconSize: 'sm' as const
106
+ },
107
+ };
108
+
109
+ const sizeConfig = sizeMap[size];
110
+
111
+ // Color mapping
112
+ const colorMap = {
113
+ primary: {
114
+ filled: { bg: tokens.colors.primary, text: tokens.colors.onPrimary, border: tokens.colors.primary },
115
+ outlined: { bg: 'transparent', text: tokens.colors.primary, border: tokens.colors.primary },
116
+ soft: { bg: tokens.colors.primaryContainer, text: tokens.colors.onPrimaryContainer, border: 'transparent' },
117
+ },
118
+ secondary: {
119
+ filled: { bg: tokens.colors.secondary, text: tokens.colors.onSecondary, border: tokens.colors.secondary },
120
+ outlined: { bg: 'transparent', text: tokens.colors.secondary, border: tokens.colors.secondary },
121
+ soft: { bg: tokens.colors.secondaryContainer, text: tokens.colors.onSecondaryContainer, border: 'transparent' },
122
+ },
123
+ success: {
124
+ filled: { bg: tokens.colors.success, text: tokens.colors.onSuccess, border: tokens.colors.success },
125
+ outlined: { bg: 'transparent', text: tokens.colors.success, border: tokens.colors.success },
126
+ soft: { bg: tokens.colors.successContainer, text: tokens.colors.onSuccessContainer, border: 'transparent' },
127
+ },
128
+ warning: {
129
+ filled: { bg: tokens.colors.warning, text: tokens.colors.onWarning, border: tokens.colors.warning },
130
+ outlined: { bg: 'transparent', text: tokens.colors.warning, border: tokens.colors.warning },
131
+ soft: { bg: tokens.colors.warningContainer, text: tokens.colors.onWarningContainer, border: 'transparent' },
132
+ },
133
+ error: {
134
+ filled: { bg: tokens.colors.error, text: tokens.colors.onError, border: tokens.colors.error },
135
+ outlined: { bg: 'transparent', text: tokens.colors.error, border: tokens.colors.error },
136
+ soft: { bg: tokens.colors.errorContainer, text: tokens.colors.onErrorContainer, border: 'transparent' },
137
+ },
138
+ info: {
139
+ filled: { bg: tokens.colors.info, text: tokens.colors.onInfo, border: tokens.colors.info },
140
+ outlined: { bg: 'transparent', text: tokens.colors.info, border: tokens.colors.info },
141
+ soft: { bg: tokens.colors.infoContainer, text: tokens.colors.onInfoContainer, border: 'transparent' },
142
+ },
143
+ };
144
+
145
+ const colorConfig = colorMap[color][variant];
146
+
147
+ // Apply custom colors if provided
148
+ const finalBackgroundColor = backgroundColor || colorConfig.bg;
149
+ const finalTextColor = textColor || colorConfig.text;
150
+ const finalBorderColor = borderColor || colorConfig.border;
151
+
152
+ // Handle disabled state
153
+ const isDisabled = disabled || (!clickable && !onPress);
154
+ const opacity = isDisabled ? 0.5 : 1;
155
+
156
+ // Handle selected state
157
+ const selectedStyle = selected ? {
158
+ borderWidth: tokens.borders.width.medium,
159
+ borderColor: tokens.colors.primary,
160
+ } : {};
161
+
162
+ const chipStyle: ViewStyle = {
163
+ flexDirection: 'row',
164
+ alignItems: 'center',
165
+ justifyContent: 'center',
166
+ paddingHorizontal: sizeConfig.paddingHorizontal,
167
+ paddingVertical: sizeConfig.paddingVertical,
168
+ backgroundColor: finalBackgroundColor,
169
+ borderRadius: tokens.borders.radius.xl,
170
+ borderWidth: variant === 'outlined' ? 1 : 0,
171
+ borderColor: finalBorderColor,
172
+ opacity,
173
+ ...selectedStyle,
174
+ };
175
+
176
+ const textStyle = {
177
+ fontSize: sizeConfig.fontSize,
178
+ fontWeight: tokens.typography.medium,
179
+ };
180
+
181
+ const iconColor = finalTextColor;
182
+
183
+ const content = (
184
+ <View style={[chipStyle, style]} testID={testID}>
185
+ {leadingIcon && (
186
+ <AtomicIcon
187
+ name={leadingIcon}
188
+ size={sizeConfig.iconSize}
189
+ customColor={iconColor}
190
+ style={{ marginRight: tokens.spacing.xs }}
191
+ />
192
+ )}
193
+ <AtomicText
194
+ type="labelMedium"
195
+ color={finalTextColor}
196
+ style={textStyle}
197
+ >
198
+ {children}
199
+ </AtomicText>
200
+ {trailingIcon && (
201
+ <AtomicIcon
202
+ name={trailingIcon}
203
+ size={sizeConfig.iconSize}
204
+ customColor={iconColor}
205
+ style={{ marginLeft: tokens.spacing.xs }}
206
+ />
207
+ )}
208
+ </View>
209
+ );
210
+
211
+ if (clickable && onPress && !disabled) {
212
+ return (
213
+ <TouchableOpacity onPress={onPress} activeOpacity={activeOpacity}>
214
+ {content}
215
+ </TouchableOpacity>
216
+ );
217
+ }
218
+
219
+ return content;
220
+ });
221
+
222
+ // =============================================================================
223
+ // EXPORTS
224
+ // =============================================================================
225
+
226
+ export default AtomicChip;
@@ -0,0 +1,255 @@
1
+ /**
2
+ * AtomicDatePicker Component
3
+ *
4
+ * A reusable date picker component that wraps the native date picker
5
+ * with consistent styling and behavior across platforms.
6
+ *
7
+ * Features:
8
+ * - Platform-specific native pickers (iOS wheel, Android dialog)
9
+ * - Consistent styling with design tokens
10
+ * - Locale-aware date/time formatting (native Date methods)
11
+ * - Timezone-aware (respects device timezone)
12
+ * - Automatic language integration (native locale support)
13
+ * - Optional label and error states
14
+ * - Minimum and maximum date constraints
15
+ * - Disabled state support
16
+ * - Theme-aware styling
17
+ * - Proper keyboard avoidance on iOS
18
+ *
19
+ * Usage:
20
+ * ```tsx
21
+ * const [selectedDate, setSelectedDate] = useState(new Date());
22
+ *
23
+ * <AtomicDatePicker
24
+ * value={selectedDate}
25
+ * onChange={setSelectedDate}
26
+ * label="Birth Date"
27
+ * minimumDate={new Date(1900, 0, 1)}
28
+ * maximumDate={new Date()}
29
+ * />
30
+ * ```
31
+ *
32
+ * Platform Behavior:
33
+ * - Opens bottom sheet from bottom with spinner wheel
34
+ * - Requires "Done" button to confirm selection
35
+ * - Can be dismissed by swiping down or tapping backdrop
36
+ *
37
+ * @module AtomicDatePicker
38
+ */
39
+
40
+ import React, { useState, useMemo } from 'react';
41
+ import {
42
+ View,
43
+ Text,
44
+ StyleSheet,
45
+ Platform,
46
+ type StyleProp,
47
+ type ViewStyle,
48
+ } from 'react-native';
49
+ import DateTimePicker, { DateTimePickerEvent } from '@react-native-community/datetimepicker';
50
+ import { useAppDesignTokens } from '../theme';
51
+ import { AtomicText } from './AtomicText';
52
+ import { DatePickerModal } from './datepicker/components/DatePickerModal';
53
+ import { DatePickerButton } from './datepicker/components/DatePickerButton';
54
+
55
+ /**
56
+ * Props for AtomicDatePicker component
57
+ */
58
+ export interface AtomicDatePickerProps {
59
+ /** Selected date value */
60
+ value: Date | null;
61
+ /** Callback when date changes */
62
+ onChange: (date: Date) => void;
63
+ /** Optional label displayed above picker */
64
+ label?: string;
65
+ /** Optional error message displayed below picker */
66
+ error?: string;
67
+ /** Disable picker interaction */
68
+ disabled?: boolean;
69
+ /** Minimum selectable date */
70
+ minimumDate?: Date;
71
+ /** Maximum selectable date */
72
+ maximumDate?: Date;
73
+ /** Picker mode - date, time, or datetime (iOS only) */
74
+ mode?: 'date' | 'time' | 'datetime';
75
+ /** Placeholder text when no value selected */
76
+ placeholder?: string;
77
+ /** Optional test ID for E2E testing */
78
+ testID?: string;
79
+ /** Optional container style */
80
+ style?: StyleProp<ViewStyle>;
81
+ }
82
+
83
+ /**
84
+ * AtomicDatePicker - Universal date/time picker component
85
+ *
86
+ * Wraps @react-native-community/datetimepicker with:
87
+ * - Theme integration
88
+ * - Platform-specific modal handling
89
+ * - Error states
90
+ * - Disabled states
91
+ * - Responsive sizing
92
+ */
93
+ export const AtomicDatePicker: React.FC<AtomicDatePickerProps> = ({
94
+ value,
95
+ onChange,
96
+ label,
97
+ error,
98
+ disabled = false,
99
+ minimumDate,
100
+ maximumDate,
101
+ mode = 'date',
102
+ placeholder = 'Select date',
103
+ testID,
104
+ style,
105
+ }) => {
106
+ const tokens = useAppDesignTokens();
107
+ const [showPicker, setShowPicker] = useState(false);
108
+
109
+ /**
110
+ * Handle date/time change in picker
111
+ * On Android, directly apply the change. On iOS, show picker in modal and apply on confirm.
112
+ */
113
+ const handleChange = (event: DateTimePickerEvent, selectedDate?: Date) => {
114
+ if (Platform.OS === 'android') {
115
+ setShowPicker(false);
116
+ if (event.type === 'set' && selectedDate) {
117
+ onChange(selectedDate);
118
+ }
119
+ } else {
120
+ // iOS: Update value while picker is open
121
+ if (event.type === 'set' && selectedDate) {
122
+ onChange(selectedDate);
123
+ }
124
+ // iOS: Close on dismiss (swipe down)
125
+ if (event.type === 'dismissed') {
126
+ setShowPicker(false);
127
+ }
128
+ }
129
+ };
130
+
131
+ /**
132
+ * Handle open - show native picker
133
+ */
134
+ const handleOpen = () => {
135
+ if (Platform.OS === 'android') {
136
+ setShowPicker(true);
137
+ } else {
138
+ // iOS: Show picker inline
139
+ setShowPicker(true);
140
+ }
141
+ };
142
+
143
+ /**
144
+ * Format date based on mode
145
+ * Uses native Date formatting (locale-aware)
146
+ */
147
+ const formatDate = useMemo(() => (date: Date): string => {
148
+ if (mode === 'time') {
149
+ return date.toLocaleTimeString([], {
150
+ hour: '2-digit',
151
+ minute: '2-digit'
152
+ });
153
+ }
154
+ if (mode === 'datetime') {
155
+ const dateStr = date.toLocaleDateString([], {
156
+ year: 'numeric',
157
+ month: 'short',
158
+ day: 'numeric',
159
+ });
160
+ const timeStr = date.toLocaleTimeString([], {
161
+ hour: '2-digit',
162
+ minute: '2-digit'
163
+ });
164
+ return `${dateStr} ${timeStr}`;
165
+ }
166
+ return date.toLocaleDateString([], {
167
+ year: 'numeric',
168
+ month: 'long',
169
+ day: 'numeric',
170
+ });
171
+ }, [mode]);
172
+
173
+ /**
174
+ * Get display text for the button
175
+ */
176
+ const displayText = useMemo(() => {
177
+ if (!value) return placeholder;
178
+ return formatDate(value);
179
+ }, [value, placeholder, formatDate]);
180
+
181
+ const styles = getStyles(tokens);
182
+
183
+ return (
184
+ <View style={[styles.container, style]} testID={testID}>
185
+ {label && (
186
+ <AtomicText style={styles.label} testID={testID ? `${testID}-label` : undefined}>
187
+ {label}
188
+ </AtomicText>
189
+ )}
190
+
191
+ <DatePickerButton
192
+ onPress={handleOpen}
193
+ disabled={disabled}
194
+ displayText={displayText}
195
+ hasValue={!!value}
196
+ error={!!error}
197
+ testID={testID ? `${testID}-button` : undefined}
198
+ />
199
+
200
+ {error && (
201
+ <AtomicText style={styles.errorText} testID={testID ? `${testID}-error` : undefined}>
202
+ {error}
203
+ </AtomicText>
204
+ )}
205
+
206
+ {/* iOS Modal */}
207
+ <DatePickerModal
208
+ visible={Platform.OS === 'ios' && showPicker}
209
+ onClose={() => setShowPicker(false)}
210
+ onDateChange={handleChange}
211
+ currentDate={value ?? new Date()}
212
+ mode={mode}
213
+ minimumDate={minimumDate}
214
+ maximumDate={maximumDate}
215
+ testID={testID}
216
+ />
217
+
218
+ {/* Android Picker */}
219
+ {Platform.OS === 'android' && showPicker && (
220
+ <DateTimePicker
221
+ value={value ?? new Date()}
222
+ mode={mode}
223
+ display="default"
224
+ onChange={handleChange}
225
+ minimumDate={minimumDate}
226
+ maximumDate={maximumDate}
227
+ testID={testID ? `${testID}-picker` : undefined}
228
+ />
229
+ )}
230
+ </View>
231
+ );
232
+ };
233
+
234
+ /**
235
+ * Get component styles based on design tokens
236
+ */
237
+ const getStyles = (tokens: ReturnType<typeof useAppDesignTokens>) => {
238
+ return StyleSheet.create({
239
+ container: {
240
+ marginBottom: tokens.spacing.md,
241
+ },
242
+ label: {
243
+ fontSize: tokens.typography.bodyMedium.fontSize,
244
+ fontWeight: tokens.typography.semibold,
245
+ color: tokens.colors.onSurface,
246
+ marginBottom: tokens.spacing.sm,
247
+ },
248
+ errorText: {
249
+ fontSize: tokens.typography.bodySmall.fontSize,
250
+ color: tokens.colors.error,
251
+ marginTop: tokens.spacing.xs,
252
+ marginLeft: tokens.spacing.xs,
253
+ },
254
+ });
255
+ };
@@ -0,0 +1,99 @@
1
+ import React from 'react';
2
+ import { TouchableOpacity, StyleSheet } from 'react-native';
3
+ import { useAppDesignTokens } from '../theme';
4
+ import { useResponsive } from '../responsive';
5
+ import { AtomicIcon } from './AtomicIcon';
6
+ import { AtomicFabProps } from './fab/types';
7
+ import {
8
+ FAB_SIZES,
9
+ getFabVariants,
10
+ getFabIconSize,
11
+ getFabBorder,
12
+ } from './fab/styles/fabStyles';
13
+
14
+ export type { FabSize, FabVariant, FabVariantConfig, FabSizeConfig, AtomicFabProps } from './fab/types';
15
+ export { FAB_SIZES, getFabVariants, getFabIconSize, getFabBorder };
16
+
17
+ /**
18
+ * AtomicFab - Floating Action Button Component
19
+ *
20
+ * A Material Design 3 compliant FAB component for primary actions.
21
+ * Follows CLAUDE.md standards for responsive positioning.
22
+ *
23
+ * @example
24
+ * ```tsx
25
+ * // IMPORTANT: FAB must be used at screen level, NOT inside ScrollView
26
+ * <ScreenLayout>
27
+ * <ScrollView>
28
+ * {/* Your content *\/}
29
+ * </ScrollView>
30
+ * <AtomicFab
31
+ * icon="add"
32
+ * onPress={handleAddItem}
33
+ * variant="primary"
34
+ * size="md"
35
+ * />
36
+ * </ScreenLayout>
37
+ * ```
38
+ *
39
+ * Features:
40
+ * - Material Design 3 sizes (sm: 40px, md: 56px, lg: 72px)
41
+ * - Three variants: primary, secondary, surface
42
+ * - Responsive positioning (above tab bar, safe area aware)
43
+ * - Disabled state with opacity
44
+ * - Theme-aware colors from design tokens
45
+ * - Border for depth (no shadows per CLAUDE.md)
46
+ */
47
+ export const AtomicFab: React.FC<AtomicFabProps> = ({
48
+ icon,
49
+ onPress,
50
+ variant = 'primary',
51
+ size = 'md',
52
+ disabled = false,
53
+ style,
54
+ testID,
55
+ accessibilityLabel,
56
+ activeOpacity = 0.7,
57
+ }) => {
58
+ const tokens = useAppDesignTokens();
59
+ const responsive = useResponsive();
60
+ const isDisabled = disabled;
61
+
62
+ // Get configurations
63
+ const sizeConfig = FAB_SIZES[size as 'sm' | 'md' | 'lg'];
64
+ const variants = getFabVariants(tokens);
65
+ const variantConfig = variants[variant as 'primary' | 'secondary' | 'surface'];
66
+ const iconSize = getFabIconSize(size as 'sm' | 'md' | 'lg');
67
+
68
+ // Combine styles
69
+ const fabStyle = StyleSheet.flatten([
70
+ {
71
+ position: 'absolute' as const,
72
+ bottom: responsive.fabPosition.bottom,
73
+ right: responsive.fabPosition.right,
74
+ width: sizeConfig.width,
75
+ height: sizeConfig.height,
76
+ borderRadius: sizeConfig.borderRadius,
77
+ backgroundColor: variantConfig.backgroundColor,
78
+ alignItems: 'center' as const,
79
+ justifyContent: 'center' as const,
80
+ },
81
+ getFabBorder(tokens),
82
+ isDisabled ? { opacity: tokens.opacity.disabled } : undefined,
83
+ style, // Custom style override
84
+ ]);
85
+
86
+ return (
87
+ <TouchableOpacity
88
+ style={fabStyle}
89
+ onPress={onPress}
90
+ disabled={isDisabled}
91
+ activeOpacity={activeOpacity}
92
+ testID={testID}
93
+ accessibilityLabel={accessibilityLabel || `${icon} floating action button`}
94
+ accessibilityRole="button"
95
+ >
96
+ <AtomicIcon name={icon} size={iconSize} customColor={variantConfig.iconColor} />
97
+ </TouchableOpacity>
98
+ );
99
+ };