@umituz/react-native-design-system 2.6.65 → 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 +1 -1
- package/src/atoms/AtomicDatePicker.tsx +5 -63
- package/src/atoms/AtomicInput.tsx +3 -14
- package/src/atoms/AtomicPicker.tsx +10 -21
- package/src/atoms/datepicker/hooks/useDatePickerText.ts +64 -0
- package/src/atoms/datepicker/styles/datePickerStyles.ts +27 -0
- package/src/atoms/input/styles/inputStyles.ts +18 -0
- package/src/atoms/picker/components/PickerIcons.tsx +56 -0
- package/src/atoms/picker/styles/pickerModalStyles.ts +36 -0
- package/src/atoms/picker/styles/pickerOptionStyles.ts +32 -0
- package/src/atoms/picker/styles/pickerSearchStyles.ts +25 -0
- package/src/atoms/picker/styles/pickerStateStyles.ts +42 -0
- package/src/atoms/picker/styles/pickerStyles.ts +78 -184
- package/src/molecules/calendar/presentation/components/AtomicCalendar.tsx +19 -160
- package/src/molecules/calendar/presentation/components/CalendarDayCell.tsx +101 -0
- package/src/molecules/calendar/presentation/components/CalendarWeekdayHeader.tsx +28 -0
- package/src/molecules/calendar/presentation/components/calendarStyles.ts +56 -0
- package/src/theme/hooks/commonStyles/formStyles.ts +17 -0
- package/src/theme/hooks/commonStyles/layoutStyles.ts +38 -0
- package/src/theme/hooks/commonStyles/paddingStyles.ts +20 -0
- package/src/theme/hooks/commonStyles/screenContainerStyles.ts +22 -0
- package/src/theme/hooks/commonStyles/scrollContainerStyles.ts +28 -0
- package/src/theme/hooks/commonStyles/sectionStyles.ts +17 -0
- package/src/theme/hooks/commonStyles/textStyles.ts +36 -0
- package/src/theme/hooks/useCommonStyles.ts +15 -219
|
@@ -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
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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
|
-
|
|
20
|
-
|
|
19
|
+
base: TextStyle;
|
|
20
|
+
size: Record<PickerSize, TextStyle>;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
export interface PickerValueStyles {
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
base: TextStyle;
|
|
25
|
+
size: Record<PickerSize, TextStyle>;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
export interface PickerPlaceholderStyles {
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
base: TextStyle;
|
|
30
|
+
size: Record<PickerSize, TextStyle>;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
export const getPickerContainerStyles = (tokens: DesignTokens): PickerContainerStyles => ({
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
-
|
|
71
|
-
|
|
72
|
-
color: tokens.colors.textPrimary,
|
|
73
|
-
marginBottom: tokens.spacing.xs,
|
|
59
|
+
},
|
|
60
|
+
state: {
|
|
61
|
+
error: {
|
|
62
|
+
borderColor: tokens.colors.error,
|
|
74
63
|
},
|
|
75
|
-
|
|
76
|
-
|
|
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
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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
|
|
82
|
+
export const getPickerValueStyles = (tokens: DesignTokens): PickerValueStyles => ({
|
|
83
|
+
base: {
|
|
84
|
+
color: tokens.colors.textPrimary,
|
|
156
85
|
flex: 1,
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
|
|
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
|
|
177
|
-
|
|
178
|
-
color:
|
|
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
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
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
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
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
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
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,
|
|
35
|
-
import { useAppDesignTokens
|
|
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={[
|
|
113
|
-
{
|
|
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
|
-
{
|
|
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
|
-
<
|
|
121
|
+
<CalendarDayCell
|
|
140
122
|
key={index}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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
|
+
});
|