Fix CalendarInput date clearing and validation
- Expose native input field with data-testid support for test control - Support clearing dates with empty value (null/undefined) - Add ISO date format parsing (YYYY-MM-DD) for string inputs - Sync native input with Calendar selection automatically - Update styling to show native input with calendar icon button - Display elements (selected-departure-date, selected-return-date) now disappear when date is cleared
This commit is contained in:
@@ -1,30 +1,91 @@
|
||||
.calendar-input {
|
||||
width: 100%;
|
||||
|
||||
:global {
|
||||
.calendar-input__field {
|
||||
&__wrapper {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.p-calendar {
|
||||
width: 100%;
|
||||
|
||||
.p-inputtext {
|
||||
width: 100%;
|
||||
&__native-input {
|
||||
flex: 1;
|
||||
padding: 8px 12px;
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 4px;
|
||||
border-right: none;
|
||||
border-radius: 4px 0 0 4px;
|
||||
font-size: 14px;
|
||||
font-family: inherit;
|
||||
transition: border-color 0.2s ease;
|
||||
|
||||
&:focus {
|
||||
border-color: #1976d2;
|
||||
outline: none;
|
||||
box-shadow: inset 0 0 0 2px rgba(25, 118, 210, 0.1);
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
background-color: #f5f5f5;
|
||||
color: #999;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
&::placeholder {
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
&__calendar-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border: 1px solid #e0e0e0;
|
||||
border-left: none;
|
||||
border-radius: 0 4px 4px 0;
|
||||
background: white;
|
||||
padding-right: 4px;
|
||||
|
||||
&:has(:focus) {
|
||||
border-color: #1976d2;
|
||||
}
|
||||
}
|
||||
|
||||
:global {
|
||||
.calendar-input__calendar {
|
||||
width: auto;
|
||||
|
||||
.p-calendar-trigger {
|
||||
padding: 8px 12px;
|
||||
}
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
padding: 4px 8px;
|
||||
color: #1976d2;
|
||||
font-size: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: color 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
color: #1565c0;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.pi {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.p-inputtext {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.p-calendar {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react'
|
||||
import React, { useRef, useEffect, useState } from 'react'
|
||||
import { Calendar } from 'primereact/calendar'
|
||||
import './calendar-input.scss'
|
||||
|
||||
@@ -21,11 +21,92 @@ export const CalendarInput: React.FC<CalendarInputProps> = ({
|
||||
disabled = false,
|
||||
'data-testid': dataTestId,
|
||||
}) => {
|
||||
const inputRef = useRef<HTMLInputElement>(null)
|
||||
const calendarRef = useRef<any>(null)
|
||||
const [internalValue, setInternalValue] = useState<string>('')
|
||||
|
||||
// Format Date to ISO string (YYYY-MM-DD)
|
||||
const formatDateToISO = (date: Date | null): string => {
|
||||
if (!date) return ''
|
||||
const d = new Date(date)
|
||||
const month = String(d.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(d.getDate()).padStart(2, '0')
|
||||
const year = d.getFullYear()
|
||||
return `${year}-${month}-${day}`
|
||||
}
|
||||
|
||||
// Parse ISO string to Date
|
||||
const parseISOToDate = (dateStr: string): Date | null => {
|
||||
if (!dateStr || dateStr.trim() === '') return null
|
||||
|
||||
const match = dateStr.match(/^(\d{4})-(\d{2})-(\d{2})$/)
|
||||
if (!match) return null
|
||||
|
||||
const [, year, month, day] = match
|
||||
const date = new Date(parseInt(year), parseInt(month) - 1, parseInt(day))
|
||||
|
||||
// Validate date is valid
|
||||
if (isNaN(date.getTime())) return null
|
||||
|
||||
return date
|
||||
}
|
||||
|
||||
// Update input display when value prop changes
|
||||
useEffect(() => {
|
||||
const formattedValue = formatDateToISO(value)
|
||||
setInternalValue(formattedValue)
|
||||
if (inputRef.current) {
|
||||
inputRef.current.value = formattedValue
|
||||
}
|
||||
}, [value])
|
||||
|
||||
// Handle manual input change from user typing or clearing
|
||||
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const inputValue = e.target.value
|
||||
setInternalValue(inputValue)
|
||||
|
||||
// If input is cleared
|
||||
if (inputValue === '' || inputValue.trim() === '') {
|
||||
onChange(null)
|
||||
return
|
||||
}
|
||||
|
||||
// Try to parse the input
|
||||
const parsedDate = parseISOToDate(inputValue)
|
||||
if (parsedDate) {
|
||||
onChange(parsedDate)
|
||||
}
|
||||
// If invalid format, we don't call onChange - let user fix it
|
||||
}
|
||||
|
||||
// Handle calendar selection
|
||||
const handleCalendarChange = (e: any) => {
|
||||
const selectedDate = e.value as Date | null
|
||||
onChange(selectedDate)
|
||||
// Focus back on input after selection
|
||||
setTimeout(() => {
|
||||
inputRef.current?.focus()
|
||||
}, 0)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="calendar-input" data-testid={dataTestId || 'calendar-input'}>
|
||||
<div className="calendar-input__wrapper">
|
||||
<input
|
||||
ref={inputRef}
|
||||
type="text"
|
||||
className="calendar-input__native-input"
|
||||
placeholder={placeholder}
|
||||
value={internalValue}
|
||||
onChange={handleInputChange}
|
||||
disabled={disabled}
|
||||
data-testid={dataTestId}
|
||||
/>
|
||||
<div className="calendar-input__calendar-wrapper">
|
||||
<Calendar
|
||||
ref={calendarRef}
|
||||
value={value}
|
||||
onChange={(e) => onChange(e.value as Date | null)}
|
||||
onChange={handleCalendarChange}
|
||||
dateFormat="dd.mm.yy"
|
||||
minDate={minDate}
|
||||
maxDate={maxDate}
|
||||
@@ -33,8 +114,10 @@ export const CalendarInput: React.FC<CalendarInputProps> = ({
|
||||
disabled={disabled}
|
||||
showIcon
|
||||
inline={false}
|
||||
className="calendar-input__field"
|
||||
className="calendar-input__calendar"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user