feat(date-picker): add clearable option and enhance date handling
- Introduced a clearable option for the DatePicker component, allowing users to easily reset the selected date. - Enhanced date handling logic to support conversion from Unix timestamps and improved value management for single and range selections. - Updated TenderListFilters to utilize the clearable feature in multiple DatePicker instances for better user experience. - Refactored useTenderListPresenter to manage date range filters more effectively, ensuring proper cleanup of cleared values.
This commit is contained in:
@@ -24,6 +24,7 @@ interface IProps<T extends FieldValues> {
|
||||
minDate?: number;
|
||||
maxDate?: number;
|
||||
timePicker?: boolean;
|
||||
clearable?: boolean;
|
||||
rules?: Omit<
|
||||
RegisterOptions<T, Path<T>>,
|
||||
"valueAsNumber" | "valueAsDate" | "setValueAs" | "disabled"
|
||||
@@ -44,6 +45,7 @@ const DatePicker = <T extends FieldValues>({
|
||||
rules,
|
||||
maxDate,
|
||||
minDate,
|
||||
clearable = false,
|
||||
}: IProps<T>) => {
|
||||
return (
|
||||
<div className="space-y-3">
|
||||
@@ -66,6 +68,50 @@ const DatePicker = <T extends FieldValues>({
|
||||
}) => {
|
||||
const error = errors[name];
|
||||
|
||||
const toUnixSeconds = (v: unknown): number | null => {
|
||||
if (v === "" || v === undefined || v === null) return null;
|
||||
const n = typeof v === "number" ? v : Number(v);
|
||||
return Number.isFinite(n) ? n : null;
|
||||
};
|
||||
|
||||
const toPickerValue = ():
|
||||
| DateObject
|
||||
| DateObject[]
|
||||
| undefined => {
|
||||
if (value === "" || value === undefined || value === null) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (multiple && Array.isArray(value)) {
|
||||
const secs = value
|
||||
.map(toUnixSeconds)
|
||||
.filter((s): s is number => s !== null);
|
||||
if (!secs.length) return undefined;
|
||||
return secs.map((unix) => new DateObject(new Date(unix * 1000)));
|
||||
}
|
||||
|
||||
if (range && Array.isArray(value)) {
|
||||
const secs = value
|
||||
.map(toUnixSeconds)
|
||||
.filter((s): s is number => s !== null);
|
||||
if (!secs.length) return undefined;
|
||||
return secs.map((unix) => new DateObject(new Date(unix * 1000)));
|
||||
}
|
||||
|
||||
if (range && !Array.isArray(value)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const unix = toUnixSeconds(value);
|
||||
if (unix === null) return undefined;
|
||||
return new DateObject(new Date(unix * 1000));
|
||||
};
|
||||
|
||||
const pickerValue = toPickerValue();
|
||||
const hasValue =
|
||||
pickerValue !== undefined &&
|
||||
(Array.isArray(pickerValue) ? pickerValue.length > 0 : true);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="relative">
|
||||
@@ -81,9 +127,11 @@ const DatePicker = <T extends FieldValues>({
|
||||
}
|
||||
onlyYearPicker={onlyYearPicker}
|
||||
multiple={multiple}
|
||||
value={pickerValue}
|
||||
onChange={(date: DateObject | DateObject[] | null) => {
|
||||
if (!date) {
|
||||
return onChange("");
|
||||
onChange("");
|
||||
return;
|
||||
}
|
||||
|
||||
if (Array.isArray(date)) {
|
||||
@@ -97,6 +145,7 @@ const DatePicker = <T extends FieldValues>({
|
||||
containerClassName="w-full"
|
||||
inputClass={cn(
|
||||
"w-full rounded-xl border border-stroke/70 bg-white/90 px-5.5 py-3 pl-12.5 text-dark shadow-[0_1px_2px_rgba(16,24,40,0.04),0_8px_20px_rgba(16,24,40,0.04)] outline-none backdrop-blur-[1px] transition-all duration-200 focus:border-primary/70 focus:bg-white focus:shadow-[0_0_0_2px_rgba(59,130,246,0.14),0_10px_24px_rgba(59,130,246,0.08)] disabled:cursor-default disabled:bg-gray-2/70 dark:border-dark-3/80 dark:bg-dark-2/85 dark:text-white dark:focus:border-primary/80 dark:focus:shadow-[0_0_0_2px_rgba(59,130,246,0.2),0_10px_24px_rgba(15,23,42,0.45)] dark:disabled:bg-dark",
|
||||
clearable && hasValue && "pr-11",
|
||||
error &&
|
||||
"border-red/80 focus:border-red focus:shadow-[0_0_0_2px_rgba(239,68,68,0.16),0_10px_24px_rgba(239,68,68,0.1)] dark:border-red/80 dark:focus:shadow-[0_0_0_2px_rgba(239,68,68,0.24),0_10px_24px_rgba(127,29,29,0.45)]",
|
||||
)}
|
||||
@@ -105,6 +154,35 @@ const DatePicker = <T extends FieldValues>({
|
||||
<span className="pointer-events-none absolute left-4.5 top-1/2 -translate-y-1/2 text-dark-5 transition-colors dark:text-dark-6">
|
||||
<CalendarIcon />
|
||||
</span>
|
||||
{clearable && hasValue && (
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Clear date"
|
||||
className="absolute right-3 top-1/2 z-10 -translate-y-1/2 rounded-md p-1 text-dark-5 transition hover:bg-gray-2 hover:text-dark dark:text-dark-6 dark:hover:bg-dark-3 dark:hover:text-white"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
onChange("");
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-hidden
|
||||
>
|
||||
<path
|
||||
d="M12 4L4 12M4 4L12 12"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
|
||||
Reference in New Issue
Block a user