@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,110 @@
1
+ /**
2
+ * AtomicIcon Tests
3
+ *
4
+ * Basic test cases for AtomicIcon component
5
+ */
6
+
7
+ import React from 'react';
8
+ import { render } from '@testing-library/react-native';
9
+ import { AtomicIcon } from '../AtomicIcon';
10
+
11
+ // Mock design tokens
12
+ jest.mock('@umituz/react-native-design-system-theme', () => ({
13
+ useAppDesignTokens: () => ({
14
+ colors: {
15
+ primary: '#007AFF',
16
+ secondary: '#8E8E93',
17
+ },
18
+ }),
19
+ }));
20
+
21
+ // Mock Lucide icons
22
+ jest.mock('lucide-react-native', () => ({
23
+ Settings: () => 'Settings-Icon',
24
+ Heart: () => 'Heart-Icon',
25
+ }));
26
+
27
+ describe('AtomicIcon', () => {
28
+ it('renders with default props', () => {
29
+ const { getByTestId } = render(
30
+ <AtomicIcon name="Settings" testID="test-icon" />
31
+ );
32
+
33
+ expect(getByTestId('test-icon')).toBeTruthy();
34
+ });
35
+
36
+ it('renders with different sizes', () => {
37
+ const { getByTestId, rerender } = render(
38
+ <AtomicIcon name="Settings" size="sm" testID="test-icon" />
39
+ );
40
+
41
+ expect(getByTestId('test-icon')).toBeTruthy();
42
+
43
+ rerender(<AtomicIcon name="Settings" size="lg" testID="test-icon" />);
44
+ expect(getByTestId('test-icon')).toBeTruthy();
45
+ });
46
+
47
+ it('renders with different colors', () => {
48
+ const { getByTestId, rerender } = render(
49
+ <AtomicIcon name="Settings" color="primary" testID="test-icon" />
50
+ );
51
+
52
+ expect(getByTestId('test-icon')).toBeTruthy();
53
+
54
+ rerender(<AtomicIcon name="Settings" color="secondary" testID="test-icon" />);
55
+ expect(getByTestId('test-icon')).toBeTruthy();
56
+ });
57
+
58
+ it('renders with custom size', () => {
59
+ const { getByTestId } = render(
60
+ <AtomicIcon name="Settings" customSize={32} testID="test-icon" />
61
+ );
62
+
63
+ expect(getByTestId('test-icon')).toBeTruthy();
64
+ });
65
+
66
+ it('renders with custom color', () => {
67
+ const { getByTestId } = render(
68
+ <AtomicIcon name="Settings" customColor="#FF0000" testID="test-icon" />
69
+ );
70
+
71
+ expect(getByTestId('test-icon')).toBeTruthy();
72
+ });
73
+
74
+ it('renders with background', () => {
75
+ const { getByTestId } = render(
76
+ <AtomicIcon
77
+ name="Settings"
78
+ withBackground
79
+ backgroundColor="#F0F0F0"
80
+ testID="test-icon"
81
+ />
82
+ );
83
+
84
+ expect(getByTestId('test-icon')).toBeTruthy();
85
+ });
86
+
87
+ it('has accessibility label when provided', () => {
88
+ const { getByTestId } = render(
89
+ <AtomicIcon
90
+ name="Settings"
91
+ accessibilityLabel="Settings icon"
92
+ testID="test-icon"
93
+ />
94
+ );
95
+
96
+ expect(getByTestId('test-icon')).toBeTruthy();
97
+ });
98
+
99
+ it('handles unknown icon names gracefully', () => {
100
+ const consoleSpy = jest.spyOn(console, 'warn').mockImplementation();
101
+
102
+ render(<AtomicIcon name="UnknownIcon" testID="test-icon" />);
103
+
104
+ expect(consoleSpy).toHaveBeenCalledWith(
105
+ expect.stringContaining('Icon "UnknownIcon" not found')
106
+ );
107
+
108
+ consoleSpy.mockRestore();
109
+ });
110
+ });
@@ -0,0 +1,195 @@
1
+ /**
2
+ * AtomicInput Tests
3
+ *
4
+ * Basic test cases for AtomicInput component
5
+ */
6
+
7
+ import React from 'react';
8
+ import { render, fireEvent } from '@testing-library/react-native';
9
+ import { AtomicInput } from '../AtomicInput';
10
+
11
+ // Mock design tokens
12
+ jest.mock('@umituz/react-native-design-system-theme', () => ({
13
+ useAppDesignTokens: () => ({
14
+ colors: {
15
+ primary: '#007AFF',
16
+ secondary: '#8E8E93',
17
+ error: '#FF3B30',
18
+ success: '#34C759',
19
+ surface: '#F2F2F7',
20
+ border: '#C6C6C8',
21
+ textPrimary: '#000000',
22
+ textSecondary: '#8E8E93',
23
+ textDisabled: '#C7C7CC',
24
+ },
25
+ spacing: {
26
+ xs: 4,
27
+ sm: 8,
28
+ md: 16,
29
+ lg: 24,
30
+ },
31
+ typography: {
32
+ bodyMedium: { fontSize: 16 },
33
+ bodySmall: { fontSize: 14 },
34
+ },
35
+ borders: {
36
+ radius: {
37
+ md: 8,
38
+ },
39
+ },
40
+ }),
41
+ }));
42
+
43
+ describe('AtomicInput', () => {
44
+ it('renders correctly with basic props', () => {
45
+ const { getByDisplayValue } = render(
46
+ <AtomicInput value="test" onChangeText={() => {}} />
47
+ );
48
+
49
+ expect(getByDisplayValue('test')).toBeTruthy();
50
+ });
51
+
52
+ it('handles text changes', () => {
53
+ const mockOnChange = jest.fn();
54
+ const { getByDisplayValue } = render(
55
+ <AtomicInput value="" onChangeText={mockOnChange} />
56
+ );
57
+
58
+ const input = getByDisplayValue('');
59
+ fireEvent.changeText(input, 'new text');
60
+
61
+ expect(mockOnChange).toHaveBeenCalledWith('new text');
62
+ });
63
+
64
+ it('renders with label', () => {
65
+ const { getByText } = render(
66
+ <AtomicInput value="" onChangeText={() => {}} label="Test Label" />
67
+ );
68
+
69
+ expect(getByText('Test Label')).toBeTruthy();
70
+ });
71
+
72
+ it('renders with placeholder', () => {
73
+ const { getByPlaceholderText } = render(
74
+ <AtomicInput value="" onChangeText={() => {}} placeholder="Enter text" />
75
+ );
76
+
77
+ expect(getByPlaceholderText('Enter text')).toBeTruthy();
78
+ });
79
+
80
+ it('renders with error state', () => {
81
+ const { getByText } = render(
82
+ <AtomicInput
83
+ value=""
84
+ onChangeText={() => {}}
85
+ state="error"
86
+ helperText="Error message"
87
+ />
88
+ );
89
+
90
+ expect(getByText('Error message')).toBeTruthy();
91
+ });
92
+
93
+ it('renders with success state', () => {
94
+ const { getByText } = render(
95
+ <AtomicInput
96
+ value=""
97
+ onChangeText={() => {}}
98
+ state="success"
99
+ helperText="Success message"
100
+ />
101
+ );
102
+
103
+ expect(getByText('Success message')).toBeTruthy();
104
+ });
105
+
106
+ it('renders with leading icon', () => {
107
+ const { getByTestId } = render(
108
+ <AtomicInput
109
+ value=""
110
+ onChangeText={() => {}}
111
+ leadingIcon="Search"
112
+ testID="test-input"
113
+ />
114
+ );
115
+
116
+ expect(getByTestId('test-input')).toBeTruthy();
117
+ });
118
+
119
+ it('renders with trailing icon', () => {
120
+ const { getByTestId } = render(
121
+ <AtomicInput
122
+ value=""
123
+ onChangeText={() => {}}
124
+ trailingIcon="Eye"
125
+ testID="test-input"
126
+ />
127
+ );
128
+
129
+ expect(getByTestId('test-input')).toBeTruthy();
130
+ });
131
+
132
+ it('renders with password toggle', () => {
133
+ const { getByTestId } = render(
134
+ <AtomicInput
135
+ value=""
136
+ onChangeText={() => {}}
137
+ secureTextEntry
138
+ showPasswordToggle
139
+ testID="test-input"
140
+ />
141
+ );
142
+
143
+ expect(getByTestId('test-input')).toBeTruthy();
144
+ });
145
+
146
+ it('is disabled when disabled prop is true', () => {
147
+ const { getByDisplayValue } = render(
148
+ <AtomicInput value="test" onChangeText={() => {}} disabled />
149
+ );
150
+
151
+ const input = getByDisplayValue('test');
152
+ expect(input).toBeDisabled();
153
+ });
154
+
155
+ it('renders with different variants', () => {
156
+ const { getByDisplayValue, rerender } = render(
157
+ <AtomicInput value="test" onChangeText={() => {}} variant="outlined" />
158
+ );
159
+
160
+ expect(getByDisplayValue('test')).toBeTruthy();
161
+
162
+ rerender(<AtomicInput value="test" onChangeText={() => {}} variant="filled" />);
163
+ expect(getByDisplayValue('test')).toBeTruthy();
164
+
165
+ rerender(<AtomicInput value="test" onChangeText={() => {}} variant="flat" />);
166
+ expect(getByDisplayValue('test')).toBeTruthy();
167
+ });
168
+
169
+ it('renders with different sizes', () => {
170
+ const { getByDisplayValue, rerender } = render(
171
+ <AtomicInput value="test" onChangeText={() => {}} size="sm" />
172
+ );
173
+
174
+ expect(getByDisplayValue('test')).toBeTruthy();
175
+
176
+ rerender(<AtomicInput value="test" onChangeText={() => {}} size="md" />);
177
+ expect(getByDisplayValue('test')).toBeTruthy();
178
+
179
+ rerender(<AtomicInput value="test" onChangeText={() => {}} size="lg" />);
180
+ expect(getByDisplayValue('test')).toBeTruthy();
181
+ });
182
+
183
+ it('shows character count when enabled', () => {
184
+ const { getByText } = render(
185
+ <AtomicInput
186
+ value="test"
187
+ onChangeText={() => {}}
188
+ showCharacterCount
189
+ maxLength={10}
190
+ />
191
+ );
192
+
193
+ expect(getByText('4/10')).toBeTruthy();
194
+ });
195
+ });
@@ -0,0 +1,112 @@
1
+ /**
2
+ * DatePickerButton Component
3
+ *
4
+ * Button component that triggers the date picker modal.
5
+ * Extracted from AtomicDatePicker for better separation of concerns.
6
+ */
7
+
8
+ import React from 'react';
9
+ import {
10
+ View,
11
+ TouchableOpacity,
12
+ StyleSheet,
13
+ } from 'react-native';
14
+ import { useAppDesignTokens } from '../../../theme';
15
+ import { AtomicIcon } from '../../AtomicIcon';
16
+ import { AtomicText } from '../../AtomicText';
17
+
18
+ interface DatePickerButtonProps {
19
+ onPress: () => void;
20
+ disabled?: boolean;
21
+ displayText: string;
22
+ hasValue: boolean;
23
+ error?: boolean;
24
+ testID?: string;
25
+ }
26
+
27
+ export const DatePickerButton: React.FC<DatePickerButtonProps> = ({
28
+ onPress,
29
+ disabled = false,
30
+ displayText,
31
+ hasValue,
32
+ error,
33
+ testID,
34
+ }) => {
35
+ const tokens = useAppDesignTokens();
36
+
37
+ const buttonStyles = StyleSheet.create({
38
+ container: {
39
+ flexDirection: 'row',
40
+ alignItems: 'center',
41
+ justifyContent: 'space-between',
42
+ paddingHorizontal: tokens.spacing.md,
43
+ paddingVertical: tokens.spacing.sm,
44
+ borderRadius: tokens.borders.radius.md,
45
+ borderWidth: 1,
46
+ backgroundColor: tokens.colors.surface,
47
+ minHeight: 48,
48
+ },
49
+ containerError: {
50
+ borderColor: tokens.colors.error,
51
+ },
52
+ containerDisabled: {
53
+ backgroundColor: tokens.colors.surfaceVariant,
54
+ borderColor: tokens.colors.outline,
55
+ opacity: 0.6,
56
+ },
57
+ containerDefault: {
58
+ borderColor: tokens.colors.outline,
59
+ },
60
+ textContainer: {
61
+ flex: 1,
62
+ },
63
+ placeholderText: {
64
+ fontSize: tokens.typography.bodyMedium.fontSize,
65
+ color: tokens.colors.textSecondary,
66
+ },
67
+ valueText: {
68
+ fontSize: tokens.typography.bodyMedium.fontSize,
69
+ color: tokens.colors.onSurface,
70
+ fontWeight: '500',
71
+ },
72
+ iconContainer: {
73
+ flexDirection: 'row',
74
+ alignItems: 'center',
75
+ gap: tokens.spacing.xs,
76
+ },
77
+ });
78
+
79
+ const containerStyle = [
80
+ buttonStyles.container,
81
+ error ? buttonStyles.containerError :
82
+ disabled ? buttonStyles.containerDisabled :
83
+ buttonStyles.containerDefault,
84
+ ];
85
+
86
+ const textStyle = hasValue ? buttonStyles.valueText : buttonStyles.placeholderText;
87
+
88
+ return (
89
+ <TouchableOpacity
90
+ onPress={onPress}
91
+ disabled={disabled}
92
+ accessibilityRole="button"
93
+ accessibilityState={{ disabled }}
94
+ testID={testID}
95
+ style={containerStyle}
96
+ >
97
+ <View style={buttonStyles.textContainer}>
98
+ <AtomicText style={textStyle} numberOfLines={1}>
99
+ {displayText}
100
+ </AtomicText>
101
+ </View>
102
+
103
+ <View style={buttonStyles.iconContainer}>
104
+ <AtomicIcon
105
+ name="Calendar"
106
+ size="md"
107
+ color={disabled ? 'surfaceVariant' : 'secondary'}
108
+ />
109
+ </View>
110
+ </TouchableOpacity>
111
+ );
112
+ };
@@ -0,0 +1,143 @@
1
+ /**
2
+ * DatePickerModal Component
3
+ *
4
+ * Modal component for iOS date picker with proper styling and behavior.
5
+ * Extracted from AtomicDatePicker for better separation of concerns.
6
+ */
7
+
8
+ import React from 'react';
9
+ import {
10
+ View,
11
+ Modal,
12
+ TouchableOpacity,
13
+ StyleSheet,
14
+ Platform,
15
+ } from 'react-native';
16
+ import { useSafeAreaInsets } from 'react-native-safe-area-context';
17
+ import DateTimePicker, { DateTimePickerEvent } from '@react-native-community/datetimepicker';
18
+ import { useAppDesignTokens } from '../../../theme';
19
+ import { AtomicIcon } from '../../AtomicIcon';
20
+ import { AtomicText } from '../../AtomicText';
21
+
22
+ interface DatePickerModalProps {
23
+ visible: boolean;
24
+ onClose: () => void;
25
+ onDateChange: (event: DateTimePickerEvent, date?: Date) => void;
26
+ currentDate: Date;
27
+ mode?: 'date' | 'time' | 'datetime';
28
+ minimumDate?: Date;
29
+ maximumDate?: Date;
30
+ overlayOpacity?: number;
31
+ titleText?: {
32
+ date?: string;
33
+ time?: string;
34
+ datetime?: string;
35
+ };
36
+ doneButtonText?: string;
37
+ testID?: string;
38
+ }
39
+
40
+ export const DatePickerModal: React.FC<DatePickerModalProps> = ({
41
+ visible,
42
+ onClose,
43
+ onDateChange,
44
+ currentDate,
45
+ mode = 'date',
46
+ minimumDate,
47
+ maximumDate,
48
+ overlayOpacity = 0.5,
49
+ titleText,
50
+ doneButtonText = 'Done',
51
+ testID,
52
+ }) => {
53
+ const tokens = useAppDesignTokens();
54
+ const insets = useSafeAreaInsets();
55
+
56
+ const modalStyles = StyleSheet.create({
57
+ overlay: {
58
+ flex: 1,
59
+ backgroundColor: `rgba(0, 0, 0, ${overlayOpacity})`,
60
+ justifyContent: 'flex-end',
61
+ },
62
+ container: {
63
+ backgroundColor: tokens.colors.surface,
64
+ borderTopLeftRadius: tokens.borders.radius.lg,
65
+ borderTopRightRadius: tokens.borders.radius.lg,
66
+ paddingBottom: insets.bottom,
67
+ },
68
+ header: {
69
+ flexDirection: 'row',
70
+ justifyContent: 'space-between',
71
+ alignItems: 'center',
72
+ paddingHorizontal: tokens.spacing.md,
73
+ paddingVertical: tokens.spacing.sm,
74
+ borderBottomWidth: 1,
75
+ borderBottomColor: tokens.colors.outline,
76
+ },
77
+ title: {
78
+ fontSize: tokens.typography.titleLarge.fontSize,
79
+ fontWeight: '600',
80
+ color: tokens.colors.onSurface,
81
+ },
82
+ doneButton: {
83
+ paddingHorizontal: tokens.spacing.md,
84
+ paddingVertical: tokens.spacing.xs,
85
+ borderRadius: tokens.borders.radius.md,
86
+ backgroundColor: tokens.colors.primary,
87
+ },
88
+ doneButtonText: {
89
+ fontSize: tokens.typography.labelMedium.fontSize,
90
+ fontWeight: '500',
91
+ color: tokens.colors.onPrimary,
92
+ },
93
+ });
94
+
95
+ if (Platform.OS !== 'ios') {
96
+ return null;
97
+ }
98
+
99
+ return (
100
+ <Modal
101
+ visible={visible}
102
+ transparent
103
+ animationType="slide"
104
+ onRequestClose={onClose}
105
+ testID={`${testID}-modal`}
106
+ >
107
+ <View style={modalStyles.overlay}>
108
+ <View style={modalStyles.container}>
109
+ {/* Header */}
110
+ <View style={modalStyles.header}>
111
+ <AtomicText style={modalStyles.title}>
112
+ {mode === 'date'
113
+ ? (titleText?.date || 'Select Date')
114
+ : mode === 'time'
115
+ ? (titleText?.time || 'Select Time')
116
+ : (titleText?.datetime || 'Select Date & Time')
117
+ }
118
+ </AtomicText>
119
+ <TouchableOpacity
120
+ onPress={onClose}
121
+ style={modalStyles.doneButton}
122
+ testID={`${testID}-done`}
123
+ >
124
+ <AtomicText style={modalStyles.doneButtonText}>{doneButtonText}</AtomicText>
125
+ </TouchableOpacity>
126
+ </View>
127
+
128
+ {/* Date Picker */}
129
+ <DateTimePicker
130
+ value={currentDate}
131
+ mode={mode}
132
+ onChange={onDateChange}
133
+ minimumDate={minimumDate}
134
+ maximumDate={maximumDate}
135
+ display="spinner"
136
+ style={{ alignSelf: 'center' }}
137
+ testID={`${testID}-picker`}
138
+ />
139
+ </View>
140
+ </View>
141
+ </Modal>
142
+ );
143
+ };
@@ -0,0 +1,98 @@
1
+ /**
2
+ * FAB (Floating Action Button) Styles
3
+ *
4
+ * Material Design 3 compliant FAB sizing and styling
5
+ * Used by AtomicFab component
6
+ */
7
+
8
+ import type { ViewStyle } from 'react-native';
9
+ import type { FabSizeConfig, FabVariantConfig } from '../types';
10
+
11
+ /**
12
+ * FAB size configurations based on Material Design 3
13
+ * - sm: Small FAB (40x40)
14
+ * - md: Regular FAB (56x56) - Default
15
+ * - lg: Large FAB (72x72)
16
+ */
17
+ export const FAB_SIZES: Record<'sm' | 'md' | 'lg', FabSizeConfig> = {
18
+ sm: {
19
+ width: 40,
20
+ height: 40,
21
+ borderRadius: 12,
22
+ },
23
+ md: {
24
+ width: 56,
25
+ height: 56,
26
+ borderRadius: 16,
27
+ },
28
+ lg: {
29
+ width: 72,
30
+ height: 72,
31
+ borderRadius: 20,
32
+ },
33
+ } as const;
34
+
35
+ /**
36
+ * Get FAB variant configurations based on design tokens
37
+ * @param tokens - Design tokens from theme
38
+ * @returns Variant configurations for primary, secondary, and surface
39
+ */
40
+ export function getFabVariants(tokens: {
41
+ colors: {
42
+ primary: string;
43
+ onPrimary: string;
44
+ secondary: string;
45
+ onSecondary: string;
46
+ surface: string;
47
+ onSurface: string;
48
+ };
49
+ }): Record<'primary' | 'secondary' | 'surface', FabVariantConfig> {
50
+ return {
51
+ primary: {
52
+ backgroundColor: tokens.colors.primary,
53
+ iconColor: tokens.colors.onPrimary,
54
+ },
55
+ secondary: {
56
+ backgroundColor: tokens.colors.secondary,
57
+ iconColor: tokens.colors.onSecondary,
58
+ },
59
+ surface: {
60
+ backgroundColor: tokens.colors.surface,
61
+ iconColor: tokens.colors.onSurface,
62
+ },
63
+ };
64
+ }
65
+
66
+ /**
67
+ * Get icon size based on FAB size
68
+ * @param size - FAB size variant
69
+ * @returns Icon size in pixels
70
+ */
71
+ export function getFabIconSize(size: 'sm' | 'md' | 'lg'): number {
72
+ switch (size) {
73
+ case 'sm':
74
+ return 20;
75
+ case 'md':
76
+ return 24;
77
+ case 'lg':
78
+ return 28;
79
+ default:
80
+ return 24;
81
+ }
82
+ }
83
+
84
+ /**
85
+ * Get FAB border style for depth (no shadows per CLAUDE.md)
86
+ * @param tokens - Design tokens from theme
87
+ * @returns Border style object
88
+ */
89
+ export function getFabBorder(tokens: {
90
+ colors: {
91
+ border: string;
92
+ };
93
+ }): ViewStyle {
94
+ return {
95
+ borderWidth: 1,
96
+ borderColor: tokens.colors.border,
97
+ };
98
+ }