This is a React component using React Date Picker:
import { useState, useRef, useLayoutEffect } from 'react';
import { DayPicker } from 'react-day-picker';
import { PseudoInput } from '@/components/PseudoInput';
import { Col } from '@/components/Col';
import { cn } from '@/utils';
export type DatePickerProps = {
label?: string;
value?: Date;
onChange: (value: Date) => void;
placeholder?: string;
error?: string;
className?: string;
required?: boolean;
disabled?: boolean;
small?: boolean;
};
export const DatePicker = ({
label,
value,
onChange,
placeholder = 'Select a date',
error,
className,
required,
disabled,
small = false,
}: DatePickerProps) => {
const [isOpen, setIsOpen] = useState(false);
const [dropdownPosition, setDropdownPosition] = useState<'top' | 'bottom'>(
'bottom',
);
const dropdownRef = useRef<HTMLDivElement | null>(null);
const triggerRef = useRef<HTMLDivElement | null>(null);
// Adjust dropdown position based on available space
const adjustDropdownPosition = () => {
if (triggerRef.current && dropdownRef.current) {
const triggerRect = triggerRef.current.getBoundingClientRect();
const dropdownHeight = dropdownRef.current.scrollHeight;
const viewportHeight = window.innerHeight;
const shouldFlip = triggerRect.bottom + dropdownHeight > viewportHeight;
setDropdownPosition(shouldFlip ? 'top' : 'bottom');
}
};
useLayoutEffect(() => {
if (isOpen) {
adjustDropdownPosition();
const handleResize = () => adjustDropdownPosition();
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('resize', handleResize);
};
}
}, [isOpen]);
// Close dropdown if user clicks outside
useLayoutEffect(() => {
const handleOutsideClick = (event: MouseEvent) => {
if (
dropdownRef.current &&
!dropdownRef.current.contains(event.target as Node) &&
triggerRef.current &&
!triggerRef.current.contains(event.target as Node)
) {
setIsOpen(false);
}
};
document.addEventListener('mousedown', handleOutsideClick);
return () => {
document.removeEventListener('mousedown', handleOutsideClick);
};
}, []);
return (
<Col className={cn('relative', className)}>
{/* Render label */}
{label && (
<label className="text-sm font-semibold">
{label} {required && <span className="text-danger"> *</span>}
</label>
)}
{/* PseudoInput for toggling calendar */}
<div
ref={triggerRef}
className="relative w-full"
onClick={() => !disabled && setIsOpen((prev) => !prev)}
tabIndex={0}
>
<PseudoInput
tabIndex={0}
error={error}
disabled={disabled}
className={cn(
'cursor-pointer justify-between shadow',
{
'focus-visible:ring-0 focus-visible:ring-offset-0': isOpen,
'hover:shadow-dark': !disabled,
'h-6 px-2 py-1 text-xs': small,
},
className,
)}
>
<div className="flex items-center gap-2">
<span>{value ? value.toLocaleDateString() : placeholder}</span>
</div>
</PseudoInput>
{/* Dropdown with DayPicker */}
{isOpen && (
<div
ref={dropdownRef}
className={cn(
'absolute z-10 mt-1 rounded border border-border bg-white shadow',
dropdownPosition === 'top' ? 'bottom-full mb-1' : 'top-full mt-1',
'w-full',
)}
>
<DayPicker
mode="single"
selected={value}
onSelect={(date) => {
if (date) {
onChange(date);
setIsOpen(false);
}
}}
className="rounded-md border shadow"
components={{
DayButton: ({ day, modifiers, ...props }) => (
<button
{...props}
className={cn(
'rounded p-1',
modifiers.selected
? 'bg-blue-500 text-white'
: 'hover:bg-gray-200',
)}
>
{day instanceof Date ? day.getDate() : 'Invalid Date'}
</button>
),
}}
/>
</div>
)}
</div>
{/* Error message */}
{error && <span className="text-sm text-danger">{error}</span>}
</Col>
);
};
Everything works well. I can even choose the correct date. The only issue is that all the days display as Invalid Date:
Why is this and how to fix it?