Fix form keyboard issues and sticky footers in agent screens
This commit is contained in:
@@ -0,0 +1,637 @@
|
||||
import React, { useState, useMemo, useCallback, useRef } from 'react';
|
||||
import {
|
||||
View,
|
||||
Text,
|
||||
Pressable,
|
||||
Modal,
|
||||
StyleSheet,
|
||||
Dimensions,
|
||||
ScrollView,
|
||||
PanResponder,
|
||||
} from 'react-native';
|
||||
import Ionicons from '@react-native-vector-icons/ionicons';
|
||||
import Animated, { FadeIn, FadeOut } from 'react-native-reanimated';
|
||||
|
||||
import { useTheme } from '../../theme';
|
||||
|
||||
const SCREEN_WIDTH = Dimensions.get('window').width;
|
||||
const DAY_SIZE = Math.floor((SCREEN_WIDTH - 80) / 7);
|
||||
|
||||
const MONTHS = [
|
||||
'January', 'February', 'March', 'April', 'May', 'June',
|
||||
'July', 'August', 'September', 'October', 'November', 'December',
|
||||
];
|
||||
const MONTHS_SHORT = [
|
||||
'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
|
||||
'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec',
|
||||
];
|
||||
const WEEKDAYS = ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'];
|
||||
|
||||
// Generate year range: 1950 → current year + 10
|
||||
const CURRENT_YEAR = new Date().getFullYear();
|
||||
const YEAR_RANGE_START = 1950;
|
||||
const YEAR_RANGE_END = CURRENT_YEAR + 10;
|
||||
const YEARS = Array.from({ length: YEAR_RANGE_END - YEAR_RANGE_START + 1 }, (_, i) => YEAR_RANGE_START + i);
|
||||
|
||||
interface DatePickerProps {
|
||||
label?: string;
|
||||
placeholder?: string;
|
||||
value?: string; // DD/MM/YYYY
|
||||
onDateChange: (dateStr: string) => void;
|
||||
error?: string;
|
||||
/** Max selectable date. Defaults to today */
|
||||
maxDate?: Date;
|
||||
/** Min selectable date */
|
||||
minDate?: Date;
|
||||
}
|
||||
|
||||
type PickerMode = 'days' | 'months' | 'years';
|
||||
|
||||
const parseDDMMYYYY = (str: string | undefined): Date | null => {
|
||||
if (!str) return null;
|
||||
const parts = str.split('/');
|
||||
if (parts.length !== 3) return null;
|
||||
const d = parseInt(parts[0], 10);
|
||||
const m = parseInt(parts[1], 10) - 1;
|
||||
const y = parseInt(parts[2], 10);
|
||||
if (isNaN(d) || isNaN(m) || isNaN(y)) return null;
|
||||
return new Date(y, m, d);
|
||||
};
|
||||
|
||||
const formatDDMMYYYY = (date: Date): string => {
|
||||
const d = String(date.getDate()).padStart(2, '0');
|
||||
const m = String(date.getMonth() + 1).padStart(2, '0');
|
||||
const y = date.getFullYear();
|
||||
return `${d}/${m}/${y}`;
|
||||
};
|
||||
|
||||
const isSameDay = (a: Date, b: Date) =>
|
||||
a.getDate() === b.getDate() && a.getMonth() === b.getMonth() && a.getFullYear() === b.getFullYear();
|
||||
|
||||
export const DatePicker = ({
|
||||
label,
|
||||
placeholder = 'DD/MM/YYYY',
|
||||
value,
|
||||
onDateChange,
|
||||
error,
|
||||
maxDate,
|
||||
minDate,
|
||||
}: DatePickerProps) => {
|
||||
const theme = useTheme();
|
||||
const today = useMemo(() => new Date(), []);
|
||||
|
||||
const selectedDate = useMemo(() => parseDDMMYYYY(value), [value]);
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [viewMonth, setViewMonth] = useState(selectedDate?.getMonth() ?? today.getMonth());
|
||||
const [viewYear, setViewYear] = useState(selectedDate?.getFullYear() ?? today.getFullYear());
|
||||
const [pickerMode, setPickerMode] = useState<PickerMode>('days');
|
||||
|
||||
const yearScrollRef = useRef<ScrollView>(null);
|
||||
|
||||
const openCalendar = useCallback(() => {
|
||||
const ref = selectedDate ?? today;
|
||||
setViewMonth(ref.getMonth());
|
||||
setViewYear(ref.getFullYear());
|
||||
setPickerMode('days');
|
||||
setVisible(true);
|
||||
}, [selectedDate, today]);
|
||||
|
||||
// ── Month navigation ──────────────────────────────────────
|
||||
const prevMonth = useCallback(() => {
|
||||
if (viewMonth === 0) {
|
||||
setViewMonth(11);
|
||||
setViewYear(y => y - 1);
|
||||
} else {
|
||||
setViewMonth(m => m - 1);
|
||||
}
|
||||
}, [viewMonth]);
|
||||
|
||||
const nextMonth = useCallback(() => {
|
||||
if (viewMonth === 11) {
|
||||
setViewMonth(0);
|
||||
setViewYear(y => y + 1);
|
||||
} else {
|
||||
setViewMonth(m => m + 1);
|
||||
}
|
||||
}, [viewMonth]);
|
||||
|
||||
// ── Swipe handler (PanResponder) ──────────────────────────
|
||||
const swipeThreshold = 50;
|
||||
const panResponder = useRef(
|
||||
PanResponder.create({
|
||||
onStartShouldSetPanResponder: () => false,
|
||||
onMoveShouldSetPanResponder: (_, gestureState) => {
|
||||
// Only capture horizontal swipes
|
||||
return Math.abs(gestureState.dx) > 15 && Math.abs(gestureState.dy) < 30;
|
||||
},
|
||||
onPanResponderRelease: (_, gestureState) => {
|
||||
if (gestureState.dx > swipeThreshold) {
|
||||
// Swipe right → previous month (functional updates avoid stale closure)
|
||||
setViewMonth(m => {
|
||||
if (m === 0) {
|
||||
setViewYear(y => y - 1);
|
||||
return 11;
|
||||
}
|
||||
return m - 1;
|
||||
});
|
||||
} else if (gestureState.dx < -swipeThreshold) {
|
||||
// Swipe left → next month
|
||||
setViewMonth(m => {
|
||||
if (m === 11) {
|
||||
setViewYear(y => y + 1);
|
||||
return 0;
|
||||
}
|
||||
return m + 1;
|
||||
});
|
||||
}
|
||||
},
|
||||
})
|
||||
).current;
|
||||
|
||||
// ── Day calculations ──────────────────────────────────────
|
||||
const daysInMonth = useMemo(() => new Date(viewYear, viewMonth + 1, 0).getDate(), [viewYear, viewMonth]);
|
||||
const firstDayOfWeek = useMemo(() => new Date(viewYear, viewMonth, 1).getDay(), [viewYear, viewMonth]);
|
||||
|
||||
const calendarDays = useMemo(() => {
|
||||
const days: (number | null)[] = [];
|
||||
for (let i = 0; i < firstDayOfWeek; i++) days.push(null);
|
||||
for (let d = 1; d <= daysInMonth; d++) days.push(d);
|
||||
return days;
|
||||
}, [daysInMonth, firstDayOfWeek]);
|
||||
|
||||
const isDayDisabled = useCallback(
|
||||
(day: number) => {
|
||||
const d = new Date(viewYear, viewMonth, day);
|
||||
if (maxDate && d > maxDate) return true;
|
||||
if (minDate && d < minDate) return true;
|
||||
return false;
|
||||
},
|
||||
[viewYear, viewMonth, maxDate, minDate],
|
||||
);
|
||||
|
||||
const handleSelect = (day: number) => {
|
||||
const d = new Date(viewYear, viewMonth, day);
|
||||
onDateChange(formatDDMMYYYY(d));
|
||||
setVisible(false);
|
||||
};
|
||||
|
||||
// ── Month picker handler ──────────────────────────────────
|
||||
const handleMonthSelect = (monthIndex: number) => {
|
||||
setViewMonth(monthIndex);
|
||||
setPickerMode('days');
|
||||
};
|
||||
|
||||
// ── Year picker handler ───────────────────────────────────
|
||||
const handleYearSelect = (year: number) => {
|
||||
setViewYear(year);
|
||||
setPickerMode('days');
|
||||
};
|
||||
|
||||
// Auto-scroll to current year when year picker opens
|
||||
const handleOpenYearPicker = useCallback(() => {
|
||||
setPickerMode('years');
|
||||
// Scroll to the current viewYear position after render
|
||||
setTimeout(() => {
|
||||
const yearIndex = YEARS.indexOf(viewYear);
|
||||
if (yearIndex >= 0 && yearScrollRef.current) {
|
||||
yearScrollRef.current.scrollTo({ y: Math.max(0, (yearIndex - 2) * 48), animated: false });
|
||||
}
|
||||
}, 100);
|
||||
}, [viewYear]);
|
||||
|
||||
// ── Render: Days grid ─────────────────────────────────────
|
||||
const renderDaysView = () => (
|
||||
<>
|
||||
{/* Header: tappable month & year */}
|
||||
<View style={styles.calendarHeader}>
|
||||
<Pressable onPress={prevMonth} hitSlop={12} style={[styles.navBtn, { backgroundColor: `${theme.colors.primary}10` }]}>
|
||||
<Ionicons name="chevron-back" size={20} color={theme.colors.primary} />
|
||||
</Pressable>
|
||||
|
||||
<View style={styles.monthYearWrap}>
|
||||
<Pressable onPress={() => setPickerMode('months')} style={styles.headerTappable}>
|
||||
<Text style={[styles.monthText, { color: theme.colors.primary }]}>{MONTHS[viewMonth]}</Text>
|
||||
<Ionicons name="chevron-down" size={14} color={theme.colors.primary} style={{ marginLeft: 4 }} />
|
||||
</Pressable>
|
||||
<Pressable onPress={handleOpenYearPicker} style={styles.headerTappable}>
|
||||
<Text style={[styles.yearText, { color: theme.colors.textMuted }]}>{viewYear}</Text>
|
||||
<Ionicons name="chevron-down" size={12} color={theme.colors.textMuted} style={{ marginLeft: 3 }} />
|
||||
</Pressable>
|
||||
</View>
|
||||
|
||||
<Pressable onPress={nextMonth} hitSlop={12} style={[styles.navBtn, { backgroundColor: `${theme.colors.primary}10` }]}>
|
||||
<Ionicons name="chevron-forward" size={20} color={theme.colors.primary} />
|
||||
</Pressable>
|
||||
</View>
|
||||
|
||||
{/* Weekday headers */}
|
||||
<View style={styles.weekdayRow}>
|
||||
{WEEKDAYS.map(wd => (
|
||||
<View key={wd} style={[styles.dayCell, { width: DAY_SIZE, height: 32 }]}>
|
||||
<Text style={[styles.weekdayText, { color: theme.colors.textMuted }]}>{wd}</Text>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
|
||||
{/* Days grid with swipe */}
|
||||
<View {...panResponder.panHandlers}>
|
||||
<View style={styles.daysGrid}>
|
||||
{calendarDays.map((day, idx) => {
|
||||
if (day === null) {
|
||||
return <View key={`empty-${idx}`} style={[styles.dayCell, { width: DAY_SIZE, height: DAY_SIZE }]} />;
|
||||
}
|
||||
|
||||
const dateObj = new Date(viewYear, viewMonth, day);
|
||||
const isToday = isSameDay(dateObj, today);
|
||||
const isSelected = selectedDate ? isSameDay(dateObj, selectedDate) : false;
|
||||
const disabled = isDayDisabled(day);
|
||||
|
||||
return (
|
||||
<Pressable
|
||||
key={day}
|
||||
disabled={disabled}
|
||||
onPress={() => handleSelect(day)}
|
||||
style={[
|
||||
styles.dayCell,
|
||||
{ width: DAY_SIZE, height: DAY_SIZE },
|
||||
isSelected && {
|
||||
backgroundColor: theme.colors.primary,
|
||||
borderRadius: DAY_SIZE / 2,
|
||||
},
|
||||
isToday && !isSelected && {
|
||||
borderWidth: 1.5,
|
||||
borderColor: theme.colors.primary,
|
||||
borderRadius: DAY_SIZE / 2,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Text
|
||||
style={[
|
||||
styles.dayText,
|
||||
{ color: theme.colors.textPrimary },
|
||||
isSelected && { color: theme.colors.white, fontWeight: '800' },
|
||||
isToday && !isSelected && { color: theme.colors.primary, fontWeight: '700' },
|
||||
disabled && { color: theme.colors.textMuted, opacity: 0.4 },
|
||||
]}
|
||||
>
|
||||
{day}
|
||||
</Text>
|
||||
</Pressable>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* Footer */}
|
||||
<View style={styles.calendarFooter}>
|
||||
<Pressable onPress={() => setVisible(false)} style={[styles.footerBtn, { borderColor: theme.colors.cardBorder }]}>
|
||||
<Text style={[styles.footerBtnText, { color: theme.colors.textMuted }]}>Cancel</Text>
|
||||
</Pressable>
|
||||
<Pressable
|
||||
onPress={() => {
|
||||
setViewMonth(today.getMonth());
|
||||
setViewYear(today.getFullYear());
|
||||
handleSelect(today.getDate());
|
||||
}}
|
||||
style={[styles.footerBtn, styles.todayBtn, { backgroundColor: `${theme.colors.primary}12` }]}
|
||||
>
|
||||
<Text style={[styles.footerBtnText, { color: theme.colors.primary, fontWeight: '700' }]}>Today</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
</>
|
||||
);
|
||||
|
||||
// ── Render: Month picker grid ─────────────────────────────
|
||||
const renderMonthsView = () => (
|
||||
<>
|
||||
<View style={styles.pickerHeader}>
|
||||
<Pressable onPress={() => setPickerMode('days')} hitSlop={12}>
|
||||
<Ionicons name="arrow-back" size={22} color={theme.colors.textPrimary} />
|
||||
</Pressable>
|
||||
<Text style={[styles.pickerTitle, { color: theme.colors.textPrimary }]}>Select Month</Text>
|
||||
<View style={{ width: 22 }} />
|
||||
</View>
|
||||
|
||||
<View style={styles.monthGrid}>
|
||||
{MONTHS_SHORT.map((m, idx) => {
|
||||
const isActive = idx === viewMonth;
|
||||
return (
|
||||
<Pressable
|
||||
key={m}
|
||||
onPress={() => handleMonthSelect(idx)}
|
||||
style={[
|
||||
styles.monthCell,
|
||||
{
|
||||
backgroundColor: isActive ? theme.colors.primary : `${theme.colors.primary}08`,
|
||||
borderColor: isActive ? theme.colors.primary : theme.colors.cardBorder,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Text
|
||||
style={[
|
||||
styles.monthCellText,
|
||||
{ color: isActive ? '#fff' : theme.colors.textPrimary },
|
||||
]}
|
||||
>
|
||||
{m}
|
||||
</Text>
|
||||
</Pressable>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
</>
|
||||
);
|
||||
|
||||
// ── Render: Year picker list ──────────────────────────────
|
||||
const renderYearsView = () => (
|
||||
<>
|
||||
<View style={styles.pickerHeader}>
|
||||
<Pressable onPress={() => setPickerMode('days')} hitSlop={12}>
|
||||
<Ionicons name="arrow-back" size={22} color={theme.colors.textPrimary} />
|
||||
</Pressable>
|
||||
<Text style={[styles.pickerTitle, { color: theme.colors.textPrimary }]}>Select Year</Text>
|
||||
<View style={{ width: 22 }} />
|
||||
</View>
|
||||
|
||||
<ScrollView
|
||||
ref={yearScrollRef}
|
||||
style={styles.yearList}
|
||||
showsVerticalScrollIndicator={false}
|
||||
contentContainerStyle={styles.yearListContent}
|
||||
>
|
||||
{YEARS.map(year => {
|
||||
const isActive = year === viewYear;
|
||||
return (
|
||||
<Pressable
|
||||
key={year}
|
||||
onPress={() => handleYearSelect(year)}
|
||||
style={[
|
||||
styles.yearRow,
|
||||
{
|
||||
backgroundColor: isActive ? `${theme.colors.primary}12` : 'transparent',
|
||||
borderColor: isActive ? theme.colors.primary : 'transparent',
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Text
|
||||
style={[
|
||||
styles.yearRowText,
|
||||
{
|
||||
color: isActive ? theme.colors.primary : theme.colors.textPrimary,
|
||||
fontWeight: isActive ? '800' : '500',
|
||||
},
|
||||
]}
|
||||
>
|
||||
{year}
|
||||
</Text>
|
||||
{isActive && (
|
||||
<Ionicons name="checkmark" size={20} color={theme.colors.primary} />
|
||||
)}
|
||||
</Pressable>
|
||||
);
|
||||
})}
|
||||
</ScrollView>
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
{label && (
|
||||
<Text style={[styles.label, { color: theme.colors.textSecondary }]}>
|
||||
{label.toUpperCase()}
|
||||
</Text>
|
||||
)}
|
||||
|
||||
{/* Tappable field */}
|
||||
<Pressable
|
||||
onPress={openCalendar}
|
||||
style={[
|
||||
styles.inputWrapper,
|
||||
{
|
||||
borderColor: error ? theme.colors.error : theme.colors.cardBorder,
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.02)',
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Ionicons name="calendar-outline" size={18} color={theme.colors.primary} style={{ marginRight: 10 }} />
|
||||
<Text
|
||||
style={[
|
||||
styles.inputText,
|
||||
{ color: value ? theme.colors.textPrimary : theme.colors.textMuted },
|
||||
]}
|
||||
>
|
||||
{value || placeholder}
|
||||
</Text>
|
||||
<Ionicons name="chevron-down" size={16} color={theme.colors.textMuted} />
|
||||
</Pressable>
|
||||
|
||||
{error && <Text style={[styles.errorText, { color: theme.colors.error }]}>{error}</Text>}
|
||||
|
||||
{/* Calendar Modal */}
|
||||
<Modal transparent visible={visible} animationType="fade" onRequestClose={() => setVisible(false)}>
|
||||
<Pressable style={[styles.overlay, { backgroundColor: theme.colors.overlay }]} onPress={() => setVisible(false)}>
|
||||
<Animated.View entering={FadeIn.duration(200)} exiting={FadeOut.duration(150)}>
|
||||
<Pressable
|
||||
style={[
|
||||
styles.calendarCard,
|
||||
{
|
||||
backgroundColor: theme.colors.backgroundElevated,
|
||||
borderColor: theme.colors.cardBorder,
|
||||
},
|
||||
]}
|
||||
onPress={() => {}} // prevent dismiss when tapping inside
|
||||
>
|
||||
{pickerMode === 'days' && renderDaysView()}
|
||||
{pickerMode === 'months' && renderMonthsView()}
|
||||
{pickerMode === 'years' && renderYearsView()}
|
||||
</Pressable>
|
||||
</Animated.View>
|
||||
</Pressable>
|
||||
</Modal>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
gap: 6,
|
||||
},
|
||||
label: {
|
||||
fontSize: 11,
|
||||
fontWeight: '700',
|
||||
letterSpacing: 0.5,
|
||||
marginLeft: 4,
|
||||
},
|
||||
inputWrapper: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
height: 52,
|
||||
borderRadius: 12,
|
||||
borderWidth: 1,
|
||||
paddingHorizontal: 16,
|
||||
},
|
||||
inputText: {
|
||||
flex: 1,
|
||||
fontSize: 15,
|
||||
fontWeight: '500',
|
||||
},
|
||||
errorText: {
|
||||
fontSize: 12,
|
||||
fontWeight: '500',
|
||||
marginTop: 2,
|
||||
marginLeft: 4,
|
||||
},
|
||||
overlay: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
padding: 20,
|
||||
},
|
||||
calendarCard: {
|
||||
width: SCREEN_WIDTH - 40,
|
||||
borderRadius: 20,
|
||||
borderWidth: 1,
|
||||
padding: 20,
|
||||
shadowColor: '#000',
|
||||
shadowOffset: { width: 0, height: 10 },
|
||||
shadowOpacity: 0.15,
|
||||
shadowRadius: 20,
|
||||
elevation: 10,
|
||||
},
|
||||
|
||||
// ── Header ───────────────────────────────────
|
||||
calendarHeader: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
marginBottom: 16,
|
||||
},
|
||||
navBtn: {
|
||||
width: 36,
|
||||
height: 36,
|
||||
borderRadius: 12,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
monthYearWrap: {
|
||||
alignItems: 'center',
|
||||
},
|
||||
headerTappable: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
paddingHorizontal: 8,
|
||||
paddingVertical: 3,
|
||||
},
|
||||
monthText: {
|
||||
fontSize: 18,
|
||||
fontWeight: '800',
|
||||
},
|
||||
yearText: {
|
||||
fontSize: 13,
|
||||
fontWeight: '600',
|
||||
marginTop: 2,
|
||||
},
|
||||
|
||||
// ── Days grid ────────────────────────────────
|
||||
weekdayRow: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-around',
|
||||
marginBottom: 4,
|
||||
},
|
||||
weekdayText: {
|
||||
fontSize: 12,
|
||||
fontWeight: '700',
|
||||
letterSpacing: 0.5,
|
||||
},
|
||||
daysGrid: {
|
||||
flexDirection: 'row',
|
||||
flexWrap: 'wrap',
|
||||
justifyContent: 'flex-start',
|
||||
},
|
||||
dayCell: {
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
dayText: {
|
||||
fontSize: 14,
|
||||
fontWeight: '500',
|
||||
},
|
||||
|
||||
// ── Footer ───────────────────────────────────
|
||||
calendarFooter: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'flex-end',
|
||||
gap: 12,
|
||||
marginTop: 16,
|
||||
paddingTop: 12,
|
||||
borderTopWidth: 1,
|
||||
borderTopColor: 'rgba(0,0,0,0.05)',
|
||||
},
|
||||
footerBtn: {
|
||||
paddingHorizontal: 20,
|
||||
paddingVertical: 10,
|
||||
borderRadius: 10,
|
||||
borderWidth: 1,
|
||||
borderColor: 'transparent',
|
||||
},
|
||||
todayBtn: {
|
||||
borderWidth: 0,
|
||||
},
|
||||
footerBtnText: {
|
||||
fontSize: 14,
|
||||
fontWeight: '600',
|
||||
},
|
||||
|
||||
// ── Month / Year picker shared ───────────────
|
||||
pickerHeader: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
marginBottom: 20,
|
||||
},
|
||||
pickerTitle: {
|
||||
fontSize: 17,
|
||||
fontWeight: '800',
|
||||
},
|
||||
|
||||
// ── Month picker grid ────────────────────────
|
||||
monthGrid: {
|
||||
flexDirection: 'row',
|
||||
flexWrap: 'wrap',
|
||||
gap: 10,
|
||||
justifyContent: 'center',
|
||||
},
|
||||
monthCell: {
|
||||
width: (SCREEN_WIDTH - 100) / 3,
|
||||
paddingVertical: 14,
|
||||
borderRadius: 12,
|
||||
borderWidth: 1,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
monthCellText: {
|
||||
fontSize: 15,
|
||||
fontWeight: '700',
|
||||
},
|
||||
|
||||
// ── Year picker list ─────────────────────────
|
||||
yearList: {
|
||||
maxHeight: 300,
|
||||
},
|
||||
yearListContent: {
|
||||
paddingBottom: 8,
|
||||
},
|
||||
yearRow: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
paddingVertical: 12,
|
||||
paddingHorizontal: 20,
|
||||
borderRadius: 10,
|
||||
borderWidth: 1.5,
|
||||
marginBottom: 4,
|
||||
},
|
||||
yearRowText: {
|
||||
fontSize: 16,
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user