@umituz/react-native-design-system 2.6.66 → 2.6.67

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-design-system",
3
- "version": "2.6.66",
3
+ "version": "2.6.67",
4
4
  "description": "Universal design system for React Native apps - Consolidated package with atoms, molecules, organisms, theme, typography, responsive and safe area utilities",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Picker Modal Styles
3
+ */
4
+
5
+ import type { ViewStyle, TextStyle } from 'react-native';
6
+ import type { DesignTokens } from '../../../theme';
7
+
8
+ export const getModalOverlayStyles = (): ViewStyle => ({
9
+ flex: 1,
10
+ backgroundColor: 'rgba(0, 0, 0, 0.5)',
11
+ justifyContent: 'flex-end',
12
+ });
13
+
14
+ export const getModalContainerStyles = (tokens: DesignTokens, bottomInset: number): ViewStyle => ({
15
+ backgroundColor: tokens.colors.surface,
16
+ borderTopLeftRadius: tokens.borders.radius.lg,
17
+ borderTopRightRadius: tokens.borders.radius.lg,
18
+ maxHeight: '80%',
19
+ paddingBottom: bottomInset,
20
+ });
21
+
22
+ export const getModalHeaderStyles = (tokens: DesignTokens): ViewStyle => ({
23
+ flexDirection: 'row',
24
+ justifyContent: 'space-between',
25
+ alignItems: 'center',
26
+ paddingHorizontal: tokens.spacing.md,
27
+ paddingVertical: tokens.spacing.md,
28
+ borderBottomWidth: 1,
29
+ borderBottomColor: tokens.colors.outline,
30
+ });
31
+
32
+ export const getModalTitleStyles = (tokens: DesignTokens): TextStyle => ({
33
+ fontSize: tokens.typography.titleLarge.responsiveFontSize,
34
+ fontWeight: '600',
35
+ color: tokens.colors.onSurface,
36
+ });
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Picker Option Styles
3
+ */
4
+
5
+ import type { ViewStyle, TextStyle } from 'react-native';
6
+ import type { DesignTokens } from '../../../theme';
7
+
8
+ export const getOptionContainerStyles = (
9
+ tokens: DesignTokens,
10
+ selected: boolean,
11
+ disabled: boolean
12
+ ): ViewStyle => ({
13
+ flexDirection: 'row',
14
+ alignItems: 'center',
15
+ paddingHorizontal: tokens.spacing.md,
16
+ paddingVertical: tokens.spacing.md,
17
+ gap: tokens.spacing.md,
18
+ backgroundColor: selected ? tokens.colors.surfaceVariant : 'transparent',
19
+ opacity: disabled ? tokens.opacity.disabled : 1,
20
+ });
21
+
22
+ export const getOptionTextStyles = (tokens: DesignTokens, selected: boolean): TextStyle => ({
23
+ fontSize: tokens.typography.bodyLarge.responsiveFontSize,
24
+ color: selected ? tokens.colors.primary : tokens.colors.onSurface,
25
+ fontWeight: selected ? '600' : '400',
26
+ });
27
+
28
+ export const getOptionDescriptionStyles = (tokens: DesignTokens): TextStyle => ({
29
+ fontSize: tokens.typography.bodySmall.responsiveFontSize,
30
+ color: tokens.colors.textSecondary,
31
+ marginTop: tokens.spacing.xs,
32
+ });
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Picker Search Styles
3
+ */
4
+
5
+ import type { ViewStyle, TextStyle } from 'react-native';
6
+ import type { DesignTokens } from '../../../theme';
7
+
8
+ export const getSearchContainerStyles = (tokens: DesignTokens): ViewStyle => ({
9
+ flexDirection: 'row',
10
+ alignItems: 'center',
11
+ backgroundColor: tokens.colors.surfaceVariant,
12
+ borderRadius: tokens.borders.radius.md,
13
+ marginHorizontal: tokens.spacing.md,
14
+ marginVertical: tokens.spacing.sm,
15
+ paddingHorizontal: tokens.spacing.md,
16
+ paddingVertical: tokens.spacing.sm,
17
+ gap: tokens.spacing.sm,
18
+ });
19
+
20
+ export const getSearchInputStyles = (tokens: DesignTokens): TextStyle => ({
21
+ flex: 1,
22
+ fontSize: tokens.typography.bodyMedium.responsiveFontSize,
23
+ color: tokens.colors.onSurface,
24
+ paddingVertical: 0,
25
+ });
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Picker Empty State & Chip Styles
3
+ */
4
+
5
+ import type { ViewStyle, TextStyle } from 'react-native';
6
+ import type { DesignTokens } from '../../../theme';
7
+
8
+ export const getEmptyStateStyles = (tokens: DesignTokens): ViewStyle => ({
9
+ flex: 1,
10
+ justifyContent: 'center',
11
+ alignItems: 'center',
12
+ paddingVertical: tokens.spacing.xl,
13
+ gap: tokens.spacing.md,
14
+ });
15
+
16
+ export const getEmptyStateTextStyles = (tokens: DesignTokens): TextStyle => ({
17
+ fontSize: tokens.typography.bodyMedium.responsiveFontSize,
18
+ color: tokens.colors.textSecondary,
19
+ textAlign: 'center',
20
+ });
21
+
22
+ export const getChipContainerStyles = (tokens: DesignTokens): ViewStyle => ({
23
+ flexDirection: 'row',
24
+ flexWrap: 'wrap',
25
+ gap: tokens.spacing.xs,
26
+ marginTop: tokens.spacing.sm,
27
+ });
28
+
29
+ export const getChipStyles = (tokens: DesignTokens): ViewStyle => ({
30
+ flexDirection: 'row',
31
+ alignItems: 'center',
32
+ backgroundColor: tokens.colors.surfaceVariant,
33
+ borderRadius: tokens.borders.radius.full,
34
+ paddingHorizontal: tokens.spacing.sm,
35
+ paddingVertical: tokens.spacing.xs,
36
+ gap: tokens.spacing.xs,
37
+ });
38
+
39
+ export const getChipTextStyles = (tokens: DesignTokens): TextStyle => ({
40
+ fontSize: tokens.typography.bodySmall.responsiveFontSize,
41
+ color: tokens.colors.onSurface,
42
+ });
@@ -7,217 +7,111 @@ import type { DesignTokens } from '../../../theme';
7
7
  export type PickerSize = 'sm' | 'md' | 'lg';
8
8
 
9
9
  export interface PickerContainerStyles {
10
- base: ViewStyle;
11
- size: Record<PickerSize, ViewStyle>;
12
- state: {
13
- error: ViewStyle;
14
- disabled: ViewStyle;
15
- };
10
+ base: ViewStyle;
11
+ size: Record<PickerSize, ViewStyle>;
12
+ state: {
13
+ error: ViewStyle;
14
+ disabled: ViewStyle;
15
+ };
16
16
  }
17
17
 
18
18
  export interface PickerLabelStyles {
19
- base: TextStyle;
20
- size: Record<PickerSize, TextStyle>;
19
+ base: TextStyle;
20
+ size: Record<PickerSize, TextStyle>;
21
21
  }
22
22
 
23
23
  export interface PickerValueStyles {
24
- base: TextStyle;
25
- size: Record<PickerSize, TextStyle>;
24
+ base: TextStyle;
25
+ size: Record<PickerSize, TextStyle>;
26
26
  }
27
27
 
28
28
  export interface PickerPlaceholderStyles {
29
- base: TextStyle;
30
- size: Record<PickerSize, TextStyle>;
29
+ base: TextStyle;
30
+ size: Record<PickerSize, TextStyle>;
31
31
  }
32
32
 
33
33
  export const getPickerContainerStyles = (tokens: DesignTokens): PickerContainerStyles => ({
34
- base: {
35
- flexDirection: 'row',
36
- alignItems: 'center',
37
- justifyContent: 'space-between',
38
- borderWidth: 1,
39
- borderColor: tokens.colors.outline,
40
- backgroundColor: tokens.colors.surface,
41
- borderRadius: tokens.borders.radius.md,
34
+ base: {
35
+ flexDirection: 'row',
36
+ alignItems: 'center',
37
+ justifyContent: 'space-between',
38
+ borderWidth: 1,
39
+ borderColor: tokens.colors.outline,
40
+ backgroundColor: tokens.colors.surface,
41
+ borderRadius: tokens.borders.radius.md,
42
+ },
43
+ size: {
44
+ sm: {
45
+ paddingHorizontal: tokens.spacing.sm,
46
+ paddingVertical: tokens.spacing.xs,
47
+ minHeight: 36 * tokens.spacingMultiplier,
42
48
  },
43
- size: {
44
- sm: {
45
- paddingHorizontal: tokens.spacing.sm,
46
- paddingVertical: tokens.spacing.xs,
47
- minHeight: 36 * tokens.spacingMultiplier,
48
- },
49
- md: {
50
- paddingHorizontal: tokens.spacing.md,
51
- paddingVertical: tokens.spacing.sm,
52
- minHeight: 48 * tokens.spacingMultiplier,
53
- },
54
- lg: {
55
- paddingHorizontal: tokens.spacing.lg,
56
- paddingVertical: tokens.spacing.md,
57
- minHeight: 56 * tokens.spacingMultiplier,
58
- },
49
+ md: {
50
+ paddingHorizontal: tokens.spacing.md,
51
+ paddingVertical: tokens.spacing.sm,
52
+ minHeight: 48 * tokens.spacingMultiplier,
59
53
  },
60
- state: {
61
- error: {
62
- borderColor: tokens.colors.error,
63
- },
64
- disabled: {
65
- opacity: tokens.opacity.disabled,
66
- },
54
+ lg: {
55
+ paddingHorizontal: tokens.spacing.lg,
56
+ paddingVertical: tokens.spacing.md,
57
+ minHeight: 56 * tokens.spacingMultiplier,
67
58
  },
68
- });
69
-
70
- export const getPickerLabelStyles = (tokens: DesignTokens): PickerLabelStyles => ({
71
- base: {
72
- color: tokens.colors.textPrimary,
73
- marginBottom: tokens.spacing.xs,
59
+ },
60
+ state: {
61
+ error: {
62
+ borderColor: tokens.colors.error,
74
63
  },
75
- size: {
76
- sm: { fontSize: tokens.typography.bodySmall.responsiveFontSize },
77
- md: { fontSize: tokens.typography.bodyMedium.responsiveFontSize },
78
- lg: { fontSize: tokens.typography.bodyLarge.responsiveFontSize },
64
+ disabled: {
65
+ opacity: tokens.opacity.disabled,
79
66
  },
67
+ },
80
68
  });
81
69
 
82
- export const getPickerValueStyles = (tokens: DesignTokens): PickerValueStyles => ({
83
- base: {
84
- color: tokens.colors.textPrimary,
85
- flex: 1,
86
- },
87
- size: {
88
- sm: { fontSize: tokens.typography.bodySmall.responsiveFontSize },
89
- md: { fontSize: tokens.typography.bodyMedium.responsiveFontSize },
90
- lg: { fontSize: tokens.typography.bodyLarge.responsiveFontSize },
91
- },
92
- });
93
-
94
- export const getPickerPlaceholderStyles = (tokens: DesignTokens): PickerPlaceholderStyles => ({
95
- base: {
96
- color: tokens.colors.textTertiary,
97
- flex: 1,
98
- },
99
- size: {
100
- sm: { fontSize: tokens.typography.bodySmall.responsiveFontSize },
101
- md: { fontSize: tokens.typography.bodyMedium.responsiveFontSize },
102
- lg: { fontSize: tokens.typography.bodyLarge.responsiveFontSize },
103
- },
104
- });
105
-
106
- export const getPickerErrorStyles = (tokens: DesignTokens): TextStyle => ({
107
- color: tokens.colors.error,
108
- fontSize: tokens.typography.bodySmall.responsiveFontSize,
109
- marginTop: tokens.spacing.xs,
110
- });
111
-
112
- // Modal styles
113
- export const getModalOverlayStyles = (): ViewStyle => ({
114
- flex: 1,
115
- backgroundColor: 'rgba(0, 0, 0, 0.5)',
116
- justifyContent: 'flex-end',
117
- });
118
-
119
- export const getModalContainerStyles = (tokens: DesignTokens, bottomInset: number): ViewStyle => ({
120
- backgroundColor: tokens.colors.surface,
121
- borderTopLeftRadius: tokens.borders.radius.lg,
122
- borderTopRightRadius: tokens.borders.radius.lg,
123
- maxHeight: '80%',
124
- paddingBottom: bottomInset,
125
- });
126
-
127
- export const getModalHeaderStyles = (tokens: DesignTokens): ViewStyle => ({
128
- flexDirection: 'row',
129
- justifyContent: 'space-between',
130
- alignItems: 'center',
131
- paddingHorizontal: tokens.spacing.md,
132
- paddingVertical: tokens.spacing.md,
133
- borderBottomWidth: 1,
134
- borderBottomColor: tokens.colors.outline,
135
- });
136
-
137
- export const getModalTitleStyles = (tokens: DesignTokens): TextStyle => ({
138
- fontSize: tokens.typography.titleLarge.responsiveFontSize,
139
- fontWeight: '600',
140
- color: tokens.colors.onSurface,
141
- });
142
-
143
- export const getSearchContainerStyles = (tokens: DesignTokens): ViewStyle => ({
144
- flexDirection: 'row',
145
- alignItems: 'center',
146
- backgroundColor: tokens.colors.surfaceVariant,
147
- borderRadius: tokens.borders.radius.md,
148
- marginHorizontal: tokens.spacing.md,
149
- marginVertical: tokens.spacing.sm,
150
- paddingHorizontal: tokens.spacing.md,
151
- paddingVertical: tokens.spacing.sm,
152
- gap: tokens.spacing.sm,
70
+ export const getPickerLabelStyles = (tokens: DesignTokens): PickerLabelStyles => ({
71
+ base: {
72
+ color: tokens.colors.textPrimary,
73
+ marginBottom: tokens.spacing.xs,
74
+ },
75
+ size: {
76
+ sm: { fontSize: tokens.typography.bodySmall.responsiveFontSize },
77
+ md: { fontSize: tokens.typography.bodyMedium.responsiveFontSize },
78
+ lg: { fontSize: tokens.typography.bodyLarge.responsiveFontSize },
79
+ },
153
80
  });
154
81
 
155
- export const getSearchInputStyles = (tokens: DesignTokens): TextStyle => ({
82
+ export const getPickerValueStyles = (tokens: DesignTokens): PickerValueStyles => ({
83
+ base: {
84
+ color: tokens.colors.textPrimary,
156
85
  flex: 1,
157
- fontSize: tokens.typography.bodyMedium.responsiveFontSize,
158
- color: tokens.colors.onSurface,
159
- paddingVertical: 0,
160
- });
161
-
162
- export const getOptionContainerStyles = (
163
- tokens: DesignTokens,
164
- selected: boolean,
165
- disabled: boolean
166
- ): ViewStyle => ({
167
- flexDirection: 'row',
168
- alignItems: 'center',
169
- paddingHorizontal: tokens.spacing.md,
170
- paddingVertical: tokens.spacing.md,
171
- gap: tokens.spacing.md,
172
- backgroundColor: selected ? tokens.colors.surfaceVariant : 'transparent',
173
- opacity: disabled ? tokens.opacity.disabled : 1,
86
+ },
87
+ size: {
88
+ sm: { fontSize: tokens.typography.bodySmall.responsiveFontSize },
89
+ md: { fontSize: tokens.typography.bodyMedium.responsiveFontSize },
90
+ lg: { fontSize: tokens.typography.bodyLarge.responsiveFontSize },
91
+ },
174
92
  });
175
93
 
176
- export const getOptionTextStyles = (tokens: DesignTokens, selected: boolean): TextStyle => ({
177
- fontSize: tokens.typography.bodyLarge.responsiveFontSize,
178
- color: selected ? tokens.colors.primary : tokens.colors.onSurface,
179
- fontWeight: selected ? '600' : '400',
180
- });
181
-
182
- export const getOptionDescriptionStyles = (tokens: DesignTokens): TextStyle => ({
183
- fontSize: tokens.typography.bodySmall.responsiveFontSize,
184
- color: tokens.colors.textSecondary,
185
- marginTop: tokens.spacing.xs,
186
- });
187
-
188
- export const getEmptyStateStyles = (tokens: DesignTokens): ViewStyle => ({
94
+ export const getPickerPlaceholderStyles = (tokens: DesignTokens): PickerPlaceholderStyles => ({
95
+ base: {
96
+ color: tokens.colors.textTertiary,
189
97
  flex: 1,
190
- justifyContent: 'center',
191
- alignItems: 'center',
192
- paddingVertical: tokens.spacing.xl,
193
- gap: tokens.spacing.md,
194
- });
195
-
196
- export const getEmptyStateTextStyles = (tokens: DesignTokens): TextStyle => ({
197
- fontSize: tokens.typography.bodyMedium.responsiveFontSize,
198
- color: tokens.colors.textSecondary,
199
- textAlign: 'center',
98
+ },
99
+ size: {
100
+ sm: { fontSize: tokens.typography.bodySmall.responsiveFontSize },
101
+ md: { fontSize: tokens.typography.bodyMedium.responsiveFontSize },
102
+ lg: { fontSize: tokens.typography.bodyLarge.responsiveFontSize },
103
+ },
200
104
  });
201
105
 
202
- // Chip styles
203
- export const getChipContainerStyles = (tokens: DesignTokens): ViewStyle => ({
204
- flexDirection: 'row',
205
- flexWrap: 'wrap',
206
- gap: tokens.spacing.xs,
207
- marginTop: tokens.spacing.sm,
106
+ export const getPickerErrorStyles = (tokens: DesignTokens): TextStyle => ({
107
+ color: tokens.colors.error,
108
+ fontSize: tokens.typography.bodySmall.responsiveFontSize,
109
+ marginTop: tokens.spacing.xs,
208
110
  });
209
111
 
210
- export const getChipStyles = (tokens: DesignTokens): ViewStyle => ({
211
- flexDirection: 'row',
212
- alignItems: 'center',
213
- backgroundColor: tokens.colors.surfaceVariant,
214
- borderRadius: tokens.borders.radius.full,
215
- paddingHorizontal: tokens.spacing.sm,
216
- paddingVertical: tokens.spacing.xs,
217
- gap: tokens.spacing.xs,
218
- });
112
+ // Re-export modal, search, option, and chip styles
113
+ export * from './pickerModalStyles';
114
+ export * from './pickerSearchStyles';
115
+ export * from './pickerOptionStyles';
116
+ export * from './pickerStateStyles';
219
117
 
220
- export const getChipTextStyles = (tokens: DesignTokens): TextStyle => ({
221
- fontSize: tokens.typography.bodySmall.responsiveFontSize,
222
- color: tokens.colors.onSurface,
223
- });
@@ -31,10 +31,13 @@
31
31
  */
32
32
 
33
33
  import React from 'react';
34
- import { View, TouchableOpacity, StyleSheet, StyleProp, ViewStyle } from 'react-native';
35
- import { useAppDesignTokens, AtomicText } from '../../../../index';
34
+ import { View, StyleSheet, StyleProp, ViewStyle } from 'react-native';
35
+ import { useAppDesignTokens } from '../../../../index';
36
36
  import type { CalendarDay } from '../../domain/entities/CalendarDay.entity';
37
37
  import { CalendarService } from '../../infrastructure/services/CalendarService';
38
+ import { calendarStyles } from './calendarStyles';
39
+ import { CalendarWeekdayHeader } from './CalendarWeekdayHeader';
40
+ import { CalendarDayCell } from './CalendarDayCell';
38
41
 
39
42
  /**
40
43
  * AtomicCalendar Props
@@ -104,176 +107,32 @@ export const AtomicCalendar: React.FC<AtomicCalendarProps> = ({
104
107
  testID,
105
108
  }) => {
106
109
  const tokens = useAppDesignTokens();
107
-
108
- // Get weekday names (localized)
109
110
  const weekdayNames = CalendarService.getWeekdayNames();
110
111
 
111
112
  return (
112
- <View style={[styles.container, { backgroundColor: tokens.colors.surface }, style]} testID={testID}>
113
- {/* Weekday Headers */}
114
- {showWeekdayHeaders && (
115
- <View style={styles.weekdayHeader}>
116
- {weekdayNames.map((day, index) => (
117
- <View key={index} style={styles.weekdayCell}>
118
- <AtomicText
119
- type="bodySmall"
120
- color="secondary"
121
- style={styles.weekdayText}
122
- >
123
- {day}
124
- </AtomicText>
125
- </View>
126
- ))}
127
- </View>
128
- )}
113
+ <View style={[calendarStyles.container, { backgroundColor: tokens.colors.surface }, style]} testID={testID}>
114
+ {showWeekdayHeaders && <CalendarWeekdayHeader weekdayNames={weekdayNames} />}
129
115
 
130
- {/* Calendar Grid */}
131
- <View style={styles.grid}>
116
+ <View style={calendarStyles.grid}>
132
117
  {days.map((day, index) => {
133
118
  const isSelected = CalendarService.isSameDay(day.date, selectedDate);
134
- const eventCount = day.events.length;
135
- const visibleEvents = day.events.slice(0, maxEventIndicators);
136
- const hiddenEventCount = Math.max(0, eventCount - maxEventIndicators);
137
119
 
138
120
  return (
139
- <TouchableOpacity
121
+ <CalendarDayCell
140
122
  key={index}
141
- style={[
142
- styles.dayCell,
143
- {
144
- backgroundColor: isSelected
145
- ? tokens.colors.primary
146
- : 'transparent',
147
- borderColor: isSelected
148
- ? tokens.colors.primary
149
- : day.isToday
150
- ? tokens.colors.primary
151
- : tokens.colors.border,
152
- borderWidth: isSelected ? 2 : day.isToday ? 2 : 1,
153
- opacity: day.isDisabled ? 0.4 : 1,
154
- },
155
- dayStyle,
156
- ]}
157
- onPress={() => !day.isDisabled && onDateSelect(day.date)}
158
- disabled={day.isDisabled}
159
- testID={testID ? `${testID}-day-${index}` : undefined}
160
- accessibilityLabel={`${day.date.toLocaleDateString()}, ${eventCount} events`}
161
- accessibilityRole="button"
162
- accessibilityState={{ disabled: day.isDisabled, selected: isSelected }}
163
- >
164
- {/* Day Number */}
165
- <AtomicText
166
- type="bodyMedium"
167
- color={
168
- isSelected
169
- ? 'inverse'
170
- : day.isCurrentMonth
171
- ? 'primary'
172
- : 'secondary'
173
- }
174
- style={[
175
- styles.dayText,
176
- day.isToday && !isSelected && { fontWeight: 'bold' },
177
- ]}
178
- >
179
- {day.date.getDate()}
180
- </AtomicText>
181
-
182
- {/* Event Indicators */}
183
- <View style={styles.eventIndicators}>
184
- {/* Today indicator (if today and has no events) */}
185
- {day.isToday && eventCount === 0 && (
186
- <View
187
- style={[
188
- styles.eventDot,
189
- { backgroundColor: tokens.colors.success },
190
- ]}
191
- />
192
- )}
193
-
194
- {/* Event dots */}
195
- {visibleEvents.map((event, eventIndex) => (
196
- <View
197
- key={eventIndex}
198
- style={[
199
- styles.eventDot,
200
- {
201
- backgroundColor: event.color
202
- ? event.color
203
- : event.isCompleted
204
- ? tokens.colors.success
205
- : tokens.colors.primary,
206
- },
207
- ]}
208
- />
209
- ))}
210
-
211
- {/* More events count */}
212
- {showEventCount && hiddenEventCount > 0 && (
213
- <AtomicText
214
- type="bodySmall"
215
- color="secondary"
216
- style={styles.moreEventsText}
217
- >
218
- +{hiddenEventCount}
219
- </AtomicText>
220
- )}
221
- </View>
222
- </TouchableOpacity>
123
+ day={day}
124
+ index={index}
125
+ isSelected={isSelected}
126
+ selectedDate={selectedDate}
127
+ onDateSelect={onDateSelect}
128
+ maxEventIndicators={maxEventIndicators}
129
+ showEventCount={showEventCount}
130
+ dayStyle={dayStyle}
131
+ testID={testID}
132
+ />
223
133
  );
224
134
  })}
225
135
  </View>
226
136
  </View>
227
137
  );
228
138
  };
229
-
230
- const styles = StyleSheet.create({
231
- container: {
232
- borderRadius: 12,
233
- padding: 16,
234
- },
235
- weekdayHeader: {
236
- flexDirection: 'row',
237
- marginBottom: 12,
238
- },
239
- weekdayCell: {
240
- flex: 1,
241
- alignItems: 'center',
242
- },
243
- weekdayText: {
244
- textAlign: 'center',
245
- },
246
- grid: {
247
- flexDirection: 'row',
248
- flexWrap: 'wrap',
249
- },
250
- dayCell: {
251
- width: `${100 / 7}%`,
252
- aspectRatio: 1,
253
- justifyContent: 'center',
254
- alignItems: 'center',
255
- borderRadius: 8,
256
- marginBottom: 4,
257
- padding: 4,
258
- },
259
- dayText: {
260
- textAlign: 'center',
261
- },
262
- eventIndicators: {
263
- flexDirection: 'row',
264
- alignItems: 'center',
265
- justifyContent: 'center',
266
- marginTop: 4,
267
- gap: 2,
268
- flexWrap: 'wrap',
269
- },
270
- eventDot: {
271
- width: 4,
272
- height: 4,
273
- borderRadius: 2,
274
- },
275
- moreEventsText: {
276
- fontSize: 8,
277
- marginLeft: 2,
278
- },
279
- });
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Calendar Day Cell Component
3
+ */
4
+
5
+ import React from 'react';
6
+ import { TouchableOpacity, View, StyleProp, ViewStyle } from 'react-native';
7
+ import { AtomicText, useAppDesignTokens } from '../../../../index';
8
+ import type { CalendarDay } from '../../domain/entities/CalendarDay.entity';
9
+ import { CalendarService } from '../../infrastructure/services/CalendarService';
10
+ import { calendarStyles } from './calendarStyles';
11
+
12
+ interface CalendarDayCellProps {
13
+ day: CalendarDay;
14
+ index: number;
15
+ isSelected: boolean;
16
+ selectedDate: Date;
17
+ onDateSelect: (date: Date) => void;
18
+ maxEventIndicators: number;
19
+ showEventCount: boolean;
20
+ dayStyle?: StyleProp<ViewStyle>;
21
+ testID?: string;
22
+ }
23
+
24
+ export const CalendarDayCell: React.FC<CalendarDayCellProps> = ({
25
+ day,
26
+ index,
27
+ isSelected,
28
+ selectedDate,
29
+ onDateSelect,
30
+ maxEventIndicators,
31
+ showEventCount,
32
+ dayStyle,
33
+ testID,
34
+ }) => {
35
+ const tokens = useAppDesignTokens();
36
+ const eventCount = day.events.length;
37
+ const visibleEvents = day.events.slice(0, maxEventIndicators);
38
+ const hiddenEventCount = Math.max(0, eventCount - maxEventIndicators);
39
+
40
+ return (
41
+ <TouchableOpacity
42
+ key={index}
43
+ style={[
44
+ calendarStyles.dayCell,
45
+ {
46
+ backgroundColor: isSelected ? tokens.colors.primary : 'transparent',
47
+ borderColor: isSelected
48
+ ? tokens.colors.primary
49
+ : day.isToday
50
+ ? tokens.colors.primary
51
+ : tokens.colors.border,
52
+ borderWidth: isSelected ? 2 : day.isToday ? 2 : 1,
53
+ opacity: day.isDisabled ? 0.4 : 1,
54
+ },
55
+ dayStyle,
56
+ ]}
57
+ onPress={() => !day.isDisabled && onDateSelect(day.date)}
58
+ disabled={day.isDisabled}
59
+ testID={testID ? `${testID}-day-${index}` : undefined}
60
+ accessibilityLabel={`${day.date.toLocaleDateString()}, ${eventCount} events`}
61
+ accessibilityRole="button"
62
+ accessibilityState={{ disabled: day.isDisabled, selected: isSelected }}
63
+ >
64
+ <AtomicText
65
+ type="bodyMedium"
66
+ color={isSelected ? 'inverse' : day.isCurrentMonth ? 'primary' : 'secondary'}
67
+ style={[calendarStyles.dayText, day.isToday && !isSelected && { fontWeight: 'bold' }]}
68
+ >
69
+ {day.date.getDate()}
70
+ </AtomicText>
71
+
72
+ <View style={calendarStyles.eventIndicators}>
73
+ {day.isToday && eventCount === 0 && (
74
+ <View style={[calendarStyles.eventDot, { backgroundColor: tokens.colors.success }]} />
75
+ )}
76
+
77
+ {visibleEvents.map((event, eventIndex) => (
78
+ <View
79
+ key={eventIndex}
80
+ style={[
81
+ calendarStyles.eventDot,
82
+ {
83
+ backgroundColor: event.color
84
+ ? event.color
85
+ : event.isCompleted
86
+ ? tokens.colors.success
87
+ : tokens.colors.primary,
88
+ },
89
+ ]}
90
+ />
91
+ ))}
92
+
93
+ {showEventCount && hiddenEventCount > 0 && (
94
+ <AtomicText type="bodySmall" color="secondary" style={calendarStyles.moreEventsText}>
95
+ +{hiddenEventCount}
96
+ </AtomicText>
97
+ )}
98
+ </View>
99
+ </TouchableOpacity>
100
+ );
101
+ };
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Calendar Weekday Header Component
3
+ */
4
+
5
+ import React from 'react';
6
+ import { View } from 'react-native';
7
+ import { AtomicText } from '../../../../index';
8
+ import { calendarStyles } from './calendarStyles';
9
+
10
+ interface CalendarWeekdayHeaderProps {
11
+ weekdayNames: string[];
12
+ }
13
+
14
+ export const CalendarWeekdayHeader: React.FC<CalendarWeekdayHeaderProps> = ({
15
+ weekdayNames,
16
+ }) => {
17
+ return (
18
+ <View style={calendarStyles.weekdayHeader}>
19
+ {weekdayNames.map((day, index) => (
20
+ <View key={index} style={calendarStyles.weekdayCell}>
21
+ <AtomicText type="bodySmall" color="secondary" style={calendarStyles.weekdayText}>
22
+ {day}
23
+ </AtomicText>
24
+ </View>
25
+ ))}
26
+ </View>
27
+ );
28
+ };
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Calendar Component Styles
3
+ */
4
+
5
+ import { StyleSheet } from 'react-native';
6
+
7
+ export const calendarStyles = StyleSheet.create({
8
+ container: {
9
+ borderRadius: 12,
10
+ padding: 16,
11
+ },
12
+ weekdayHeader: {
13
+ flexDirection: 'row',
14
+ marginBottom: 12,
15
+ },
16
+ weekdayCell: {
17
+ flex: 1,
18
+ alignItems: 'center',
19
+ },
20
+ weekdayText: {
21
+ textAlign: 'center',
22
+ },
23
+ grid: {
24
+ flexDirection: 'row',
25
+ flexWrap: 'wrap',
26
+ },
27
+ dayCell: {
28
+ width: `${100 / 7}%`,
29
+ aspectRatio: 1,
30
+ justifyContent: 'center',
31
+ alignItems: 'center',
32
+ borderRadius: 8,
33
+ marginBottom: 4,
34
+ padding: 4,
35
+ },
36
+ dayText: {
37
+ textAlign: 'center',
38
+ },
39
+ eventIndicators: {
40
+ flexDirection: 'row',
41
+ alignItems: 'center',
42
+ justifyContent: 'center',
43
+ marginTop: 4,
44
+ gap: 2,
45
+ flexWrap: 'wrap',
46
+ },
47
+ eventDot: {
48
+ width: 4,
49
+ height: 4,
50
+ borderRadius: 2,
51
+ },
52
+ moreEventsText: {
53
+ fontSize: 8,
54
+ marginLeft: 2,
55
+ },
56
+ });
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Form Styles
3
+ */
4
+
5
+ import type { ViewStyle } from 'react-native';
6
+ import type { DesignTokens } from '../../types/ThemeTypes';
7
+
8
+ export const createFormStyles = (tokens: DesignTokens) => ({
9
+ form: {
10
+ width: '100%',
11
+ } as ViewStyle,
12
+
13
+ formHeader: {
14
+ alignItems: 'center',
15
+ marginBottom: tokens.spacing.xl,
16
+ } as ViewStyle,
17
+ });
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Layout Styles
3
+ */
4
+
5
+ import type { ViewStyle } from 'react-native';
6
+ import type { DesignTokens } from '../../types/ThemeTypes';
7
+
8
+ export const createLayoutStyles = (tokens: DesignTokens) => ({
9
+ centerContainer: {
10
+ flex: 1,
11
+ justifyContent: 'center',
12
+ alignItems: 'center',
13
+ } as ViewStyle,
14
+
15
+ centerContainerPadded: {
16
+ flex: 1,
17
+ justifyContent: 'center',
18
+ alignItems: 'center',
19
+ paddingHorizontal: tokens.spacing.xl,
20
+ } as ViewStyle,
21
+
22
+ row: {
23
+ flexDirection: 'row',
24
+ alignItems: 'center',
25
+ } as ViewStyle,
26
+
27
+ rowBetween: {
28
+ flexDirection: 'row',
29
+ alignItems: 'center',
30
+ justifyContent: 'space-between',
31
+ } as ViewStyle,
32
+
33
+ rowCenter: {
34
+ flexDirection: 'row',
35
+ alignItems: 'center',
36
+ justifyContent: 'center',
37
+ } as ViewStyle,
38
+ });
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Padding Styles
3
+ */
4
+
5
+ import type { ViewStyle } from 'react-native';
6
+ import type { DesignTokens } from '../../types/ThemeTypes';
7
+
8
+ export const createPaddingStyles = (tokens: DesignTokens) => ({
9
+ paddedHorizontal: {
10
+ paddingHorizontal: tokens.spacing.lg,
11
+ } as ViewStyle,
12
+
13
+ paddedVertical: {
14
+ paddingVertical: tokens.spacing.lg,
15
+ } as ViewStyle,
16
+
17
+ padded: {
18
+ padding: tokens.spacing.lg,
19
+ } as ViewStyle,
20
+ });
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Screen Container Styles
3
+ */
4
+
5
+ import type { ViewStyle } from 'react-native';
6
+ import type { DesignTokens } from '../../types/ThemeTypes';
7
+
8
+ export const createScreenContainerStyles = (tokens: DesignTokens) => ({
9
+ screenContainer: {
10
+ flex: 1,
11
+ backgroundColor: tokens.colors.backgroundPrimary,
12
+ } as ViewStyle,
13
+
14
+ flexContainer: {
15
+ flex: 1,
16
+ } as ViewStyle,
17
+
18
+ screenContainerSecondary: {
19
+ flex: 1,
20
+ backgroundColor: tokens.colors.backgroundSecondary,
21
+ } as ViewStyle,
22
+ });
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Scroll Container Styles
3
+ */
4
+
5
+ import type { ViewStyle } from 'react-native';
6
+ import type { DesignTokens } from '../../types/ThemeTypes';
7
+
8
+ export const createScrollContainerStyles = (tokens: DesignTokens) => ({
9
+ scrollView: {
10
+ flex: 1,
11
+ } as ViewStyle,
12
+
13
+ scrollContent: {
14
+ paddingHorizontal: tokens.spacing.lg,
15
+ paddingBottom: tokens.spacing.xl,
16
+ } as ViewStyle,
17
+
18
+ scrollContentGrow: {
19
+ flexGrow: 1,
20
+ padding: tokens.spacing.lg,
21
+ } as ViewStyle,
22
+
23
+ scrollContentCentered: {
24
+ flexGrow: 1,
25
+ padding: tokens.spacing.lg,
26
+ justifyContent: 'center',
27
+ } as ViewStyle,
28
+ });
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Section Styles
3
+ */
4
+
5
+ import type { ViewStyle } from 'react-native';
6
+ import type { DesignTokens } from '../../types/ThemeTypes';
7
+
8
+ export const createSectionStyles = (tokens: DesignTokens) => ({
9
+ section: {
10
+ marginBottom: tokens.spacing.xl,
11
+ } as ViewStyle,
12
+
13
+ sectionPadded: {
14
+ marginBottom: tokens.spacing.xl,
15
+ paddingHorizontal: tokens.spacing.lg,
16
+ } as ViewStyle,
17
+ });
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Text Styles
3
+ */
4
+
5
+ import type { TextStyle } from 'react-native';
6
+ import type { DesignTokens } from '../../types/ThemeTypes';
7
+
8
+ export const createTextStyles = (tokens: DesignTokens) => ({
9
+ screenTitle: {
10
+ ...tokens.typography.headingLarge,
11
+ color: tokens.colors.textPrimary,
12
+ marginBottom: tokens.spacing.sm,
13
+ } as TextStyle,
14
+
15
+ sectionTitle: {
16
+ ...tokens.typography.headingMedium,
17
+ color: tokens.colors.textPrimary,
18
+ marginBottom: tokens.spacing.md,
19
+ } as TextStyle,
20
+
21
+ subtitle: {
22
+ ...tokens.typography.bodyMedium,
23
+ color: tokens.colors.textSecondary,
24
+ textAlign: 'center',
25
+ } as TextStyle,
26
+
27
+ bodyText: {
28
+ ...tokens.typography.bodyMedium,
29
+ color: tokens.colors.textPrimary,
30
+ } as TextStyle,
31
+
32
+ secondaryText: {
33
+ ...tokens.typography.bodySmall,
34
+ color: tokens.colors.textSecondary,
35
+ } as TextStyle,
36
+ });
@@ -16,8 +16,14 @@
16
16
  */
17
17
 
18
18
  import { useMemo } from 'react';
19
- import type { ViewStyle, TextStyle } from 'react-native';
20
19
  import { useAppDesignTokens } from './useAppDesignTokens';
20
+ import { createScreenContainerStyles } from './commonStyles/screenContainerStyles';
21
+ import { createScrollContainerStyles } from './commonStyles/scrollContainerStyles';
22
+ import { createLayoutStyles } from './commonStyles/layoutStyles';
23
+ import { createPaddingStyles } from './commonStyles/paddingStyles';
24
+ import { createSectionStyles } from './commonStyles/sectionStyles';
25
+ import { createTextStyles } from './commonStyles/textStyles';
26
+ import { createFormStyles } from './commonStyles/formStyles';
21
27
 
22
28
  /**
23
29
  * Hook to get common styles with dynamic theme support
@@ -25,224 +31,14 @@ import { useAppDesignTokens } from './useAppDesignTokens';
25
31
  */
26
32
  export const useCommonStyles = () => {
27
33
  const tokens = useAppDesignTokens();
28
-
34
+
29
35
  return useMemo(() => ({
30
- // ========================================================================
31
- // SCREEN CONTAINERS
32
- // ========================================================================
33
- /**
34
- * Standard full-screen container
35
- * Most common pattern: flex: 1 with background color
36
- */
37
- screenContainer: {
38
- flex: 1,
39
- backgroundColor: tokens.colors.backgroundPrimary,
40
- } as ViewStyle,
41
-
42
- /**
43
- * Basic flex container without background
44
- * Use when background is set elsewhere or not needed
45
- */
46
- flexContainer: {
47
- flex: 1,
48
- } as ViewStyle,
49
-
50
- /**
51
- * Screen container with secondary background
52
- */
53
- screenContainerSecondary: {
54
- flex: 1,
55
- backgroundColor: tokens.colors.backgroundSecondary,
56
- } as ViewStyle,
57
-
58
- // ========================================================================
59
- // SCROLL CONTAINERS
60
- // ========================================================================
61
- /**
62
- * Standard ScrollView wrapper
63
- */
64
- scrollView: {
65
- flex: 1,
66
- } as ViewStyle,
67
-
68
- /**
69
- * ScrollView content container with standard padding
70
- */
71
- scrollContent: {
72
- paddingHorizontal: tokens.spacing.lg,
73
- paddingBottom: tokens.spacing.xl,
74
- } as ViewStyle,
75
-
76
- /**
77
- * ScrollView content that grows to fill available space
78
- */
79
- scrollContentGrow: {
80
- flexGrow: 1,
81
- padding: tokens.spacing.lg,
82
- } as ViewStyle,
83
-
84
- /**
85
- * Centered scroll content (for forms, onboarding screens)
86
- */
87
- scrollContentCentered: {
88
- flexGrow: 1,
89
- padding: tokens.spacing.lg,
90
- justifyContent: 'center',
91
- } as ViewStyle,
92
-
93
- // ========================================================================
94
- // LAYOUT UTILITIES
95
- // ========================================================================
96
- /**
97
- * Centered container - both horizontal and vertical
98
- * Perfect for empty states, splash screens
99
- */
100
- centerContainer: {
101
- flex: 1,
102
- justifyContent: 'center',
103
- alignItems: 'center',
104
- } as ViewStyle,
105
-
106
- /**
107
- * Centered container with padding
108
- */
109
- centerContainerPadded: {
110
- flex: 1,
111
- justifyContent: 'center',
112
- alignItems: 'center',
113
- paddingHorizontal: tokens.spacing.xl,
114
- } as ViewStyle,
115
-
116
- /**
117
- * Horizontal row layout
118
- */
119
- row: {
120
- flexDirection: 'row',
121
- alignItems: 'center',
122
- } as ViewStyle,
123
-
124
- /**
125
- * Horizontal row with space between
126
- */
127
- rowBetween: {
128
- flexDirection: 'row',
129
- alignItems: 'center',
130
- justifyContent: 'space-between',
131
- } as ViewStyle,
132
-
133
- /**
134
- * Horizontal row centered
135
- */
136
- rowCenter: {
137
- flexDirection: 'row',
138
- alignItems: 'center',
139
- justifyContent: 'center',
140
- } as ViewStyle,
141
-
142
- // ========================================================================
143
- // PADDING UTILITIES
144
- // ========================================================================
145
- /**
146
- * Standard horizontal padding
147
- */
148
- paddedHorizontal: {
149
- paddingHorizontal: tokens.spacing.lg,
150
- } as ViewStyle,
151
-
152
- /**
153
- * Standard vertical padding
154
- */
155
- paddedVertical: {
156
- paddingVertical: tokens.spacing.lg,
157
- } as ViewStyle,
158
-
159
- /**
160
- * Standard padding all sides
161
- */
162
- padded: {
163
- padding: tokens.spacing.lg,
164
- } as ViewStyle,
165
-
166
- // ========================================================================
167
- // SECTION STYLES
168
- // ========================================================================
169
- /**
170
- * Standard section container
171
- */
172
- section: {
173
- marginBottom: tokens.spacing.xl,
174
- } as ViewStyle,
175
-
176
- /**
177
- * Section with padding
178
- */
179
- sectionPadded: {
180
- marginBottom: tokens.spacing.xl,
181
- paddingHorizontal: tokens.spacing.lg,
182
- } as ViewStyle,
183
-
184
- // ========================================================================
185
- // TEXT STYLES
186
- // ========================================================================
187
- /**
188
- * Screen title - primary heading
189
- */
190
- screenTitle: {
191
- ...tokens.typography.headingLarge,
192
- color: tokens.colors.textPrimary,
193
- marginBottom: tokens.spacing.sm,
194
- } as TextStyle,
195
-
196
- /**
197
- * Section title
198
- */
199
- sectionTitle: {
200
- ...tokens.typography.headingMedium,
201
- color: tokens.colors.textPrimary,
202
- marginBottom: tokens.spacing.md,
203
- } as TextStyle,
204
-
205
- /**
206
- * Subtitle/description text
207
- */
208
- subtitle: {
209
- ...tokens.typography.bodyMedium,
210
- color: tokens.colors.textSecondary,
211
- textAlign: 'center',
212
- } as TextStyle,
213
-
214
- /**
215
- * Body text
216
- */
217
- bodyText: {
218
- ...tokens.typography.bodyMedium,
219
- color: tokens.colors.textPrimary,
220
- } as TextStyle,
221
-
222
- /**
223
- * Secondary text (muted)
224
- */
225
- secondaryText: {
226
- ...tokens.typography.bodySmall,
227
- color: tokens.colors.textSecondary,
228
- } as TextStyle,
229
-
230
- // ========================================================================
231
- // FORM STYLES
232
- // ========================================================================
233
- /**
234
- * Form container
235
- */
236
- form: {
237
- width: '100%',
238
- } as ViewStyle,
239
-
240
- /**
241
- * Form header section
242
- */
243
- formHeader: {
244
- alignItems: 'center',
245
- marginBottom: tokens.spacing.xl,
246
- } as ViewStyle,
36
+ ...createScreenContainerStyles(tokens),
37
+ ...createScrollContainerStyles(tokens),
38
+ ...createLayoutStyles(tokens),
39
+ ...createPaddingStyles(tokens),
40
+ ...createSectionStyles(tokens),
41
+ ...createTextStyles(tokens),
42
+ ...createFormStyles(tokens),
247
43
  }), [tokens]);
248
44
  };