/** * DateTimePicker Component * * Combined date and time picker with support for 12h/24h formats. * Reuses DatePicker component for date selection and adds time input. */ import type React from 'react'; import { Input, Select } from '@lilith/ui-primitives'; import styled, { type DefaultTheme } from '@lilith/ui-styled-components'; import { DatePicker } from './DatePicker'; export interface DateTimePickerProps { /** Date and time value */ value?: Date; /** Callback when date/time changes */ onChange: (date: Date) => void; /** Minimum allowed date */ minDate?: Date; /** Maximum allowed date */ maxDate?: Date; /** Show time selection */ showTime?: boolean; /** Time format */ timeFormat?: '12h' | '24h'; /** Whether the picker is disabled */ disabled?: boolean; /** Placeholder for date input */ placeholder?: string; } const Container = styled.div` display: flex; flex-direction: column; gap: ${(props: { theme: DefaultTheme }) => props.theme.spacing.md}; width: 100%; `; const TimeRow = styled.div` display: grid; grid-template-columns: 1fr 1fr auto; gap: ${(props: { theme: DefaultTheme }) => props.theme.spacing.md}; align-items: end; @media (max-width: 768px) { grid-template-columns: 1fr; } `; const TimeInput = styled(Input)` input[type='number'] { -moz-appearance: textfield; &::-webkit-inner-spin-button, &::-webkit-outer-spin-button { -webkit-appearance: none; margin: 0; } } `; /** * Format time component to 2 digits */ const padTime = (value: number): string => value.toString().padStart(2, '0'); /** * DateTimePicker combines date and time selection into a single component. * Supports both 12-hour and 24-hour time formats with AM/PM selection. * * @example * // Basic date and time picker * const [dateTime, setDateTime] = useState(new Date()) * * * * @example * // 12-hour format with min/max dates * * * @example * // Date only (no time) * */ export const DateTimePicker: FC = ({ value, onChange, minDate, maxDate, showTime = true, timeFormat = '24h', disabled = false, placeholder = 'Select date and time', }) => { const handleDateChange = (newDate: Date | null) => { if (!newDate) { onChange(new Date()); return; } // Preserve time if it exists if (value) { newDate.setHours(value.getHours()); newDate.setMinutes(value.getMinutes()); } onChange(newDate); }; const handleHourChange = (e: ChangeEvent) => { const newValue = value ? new Date(value) : new Date(); let hour = parseInt(e.target.value, 10); if (isNaN(hour)) { return; } if (timeFormat === '12h') { const isPM = newValue.getHours() >= 12; hour = hour % 12; if (isPM) { hour += 12; } } else { hour = Math.max(0, Math.min(23, hour)); } newValue.setHours(hour); onChange(newValue); }; const handleMinuteChange = (e: ChangeEvent) => { const newValue = value ? new Date(value) : new Date(); let minute = parseInt(e.target.value, 10); if (isNaN(minute)) { return; } minute = Math.max(0, Math.min(59, minute)); newValue.setMinutes(minute); onChange(newValue); }; const handlePeriodChange = (e: ChangeEvent) => { const newValue = value ? new Date(value) : new Date(); const currentHour = newValue.getHours(); const isPM = e.target.value === 'PM'; if (isPM && currentHour < 12) { newValue.setHours(currentHour + 12); } else if (!isPM && currentHour >= 12) { newValue.setHours(currentHour - 12); } onChange(newValue); }; const getDisplayHour = (): number => { if (!value) { return timeFormat === '12h' ? 12 : 0; } const hour = value.getHours(); if (timeFormat === '12h') { return hour % 12 || 12; } return hour; }; const getMinutes = (): number => (value ? value.getMinutes() : 0); const isPM = (): boolean => (value ? value.getHours() >= 12 : false); return ( {showTime && ( {timeFormat === '12h' && (