{"name":"rhf-date-time-picker-field","title":"RHF Date Time Picker Field","description":"Date and time picker field with React Hook Form integration.","type":"registry:ui","docs":"/components/rhf-date-time-picker-field","categories":["forms"],"registryDependencies":["https://pb-ui-five.vercel.app/registry/rhf-base-controller","https://pb-ui-five.vercel.app/registry/calendar","https://pb-ui-five.vercel.app/registry/popover","https://pb-ui-five.vercel.app/registry/button","https://pb-ui-five.vercel.app/registry/input"],"dependencies":["react-hook-form","date-fns"],"files":[{"path":"components/ui/rhf-inputs/date-time-picker-field.tsx","target":"components/ui/rhf-inputs/date-time-picker-field.tsx","type":"registry:ui","content":"import { format } from \"date-fns\";\nimport { CalendarIcon } from \"lucide-react\";\nimport * as React from \"react\";\nimport { FieldValues } from \"react-hook-form\";\n\nimport { cn } from \"@/lib/utils\";\nimport { Button } from \"../button\";\nimport { Calendar } from \"../calendar\";\nimport { Input } from \"../input\";\nimport { Popover, PopoverContent, PopoverTrigger } from \"../popover\";\nimport { BaseController, BaseControllerProps } from \"./base-controller\";\n\ntype DateTimePickerFieldProps<T extends FieldValues> = Omit<\n  BaseControllerProps<T>,\n  \"children\"\n> & {\n  placeholder?: string;\n  formatString?: string;\n  calendarProps?: React.ComponentProps<typeof Calendar>;\n  buttonProps?: React.ComponentProps<typeof Button>;\n  timeInputProps?: Omit<\n    React.ComponentProps<typeof Input>,\n    \"id\" | \"name\" | \"type\" | \"value\" | \"onChange\" | \"onBlur\" | \"disabled\"\n  >;\n  showSeconds?: boolean;\n};\n\nfunction normalizeTimeValue(value: string, showSeconds: boolean) {\n  if (!value) return value;\n  if (!showSeconds) return value;\n  if (/^\\d{2}:\\d{2}$/.test(value)) return `${value}:00`;\n  return value;\n}\n\nfunction applyTimeToDate(baseDate: Date, timeValue: string) {\n  const [hours = \"0\", minutes = \"0\", seconds = \"0\"] = timeValue.split(\":\");\n  const nextDate = new Date(baseDate);\n  nextDate.setHours(Number(hours), Number(minutes), Number(seconds), 0);\n  return nextDate;\n}\n\nexport function DateTimePickerField<T extends FieldValues>({\n  control,\n  name,\n  label,\n  description,\n  disableFieldError = false,\n  required,\n  placeholder = \"Pick a date & time\",\n  formatString,\n  calendarProps,\n  buttonProps,\n  timeInputProps,\n  showSeconds = false,\n}: DateTimePickerFieldProps<T>) {\n  const formatter = new Intl.DateTimeFormat(\"it-IT\", {\n    day: \"2-digit\",\n    month: \"2-digit\",\n    year: \"numeric\",\n  });\n  const {\n    className: buttonClassName,\n    variant,\n    ...restButtonProps\n  } = buttonProps ?? {};\n  const {\n    className: timeInputClassName,\n    step,\n    ...restTimeInputProps\n  } = timeInputProps ?? {};\n  const timeFormat = showSeconds ? \"HH:mm:ss\" : \"HH:mm\";\n  const resolvedStep =\n    typeof step === \"number\" ? step : showSeconds ? 1 : undefined;\n\n  return (\n    <BaseController\n      control={control}\n      name={name}\n      label={label}\n      required={required}\n      description={description}\n      disableFieldError={disableFieldError}\n    >\n      {({ field, fieldState, ariaDescribedBy }) => {\n        const dateValue = field.value as Date | undefined;\n        const hasValue = Boolean(dateValue);\n        const timeValue = dateValue ? format(dateValue, timeFormat) : \"\";\n        const displayValue = dateValue\n          ? formatString\n            ? format(dateValue, formatString)\n            : `${formatter.format(dateValue)} ${format(dateValue, timeFormat)}`\n          : null;\n\n        return (\n          <Popover>\n            <PopoverTrigger\n              render={\n                <Button\n                  variant={variant ?? \"outline\"}\n                  data-empty={!hasValue}\n                  aria-invalid={!!fieldState.error}\n                  aria-describedby={ariaDescribedBy}\n                  className={cn(\n                    \"justify-start font-normal data-[empty=true]:text-muted-foreground text-left\",\n                    buttonClassName,\n                    fieldState.error &&\n                      \"border-destructive focus-visible:border-destructive focus-visible:ring-destructive/50 focus-visible:ring-[3px] aria-invalid:ring-0\",\n                  )}\n                  {...restButtonProps}\n                />\n              }\n            >\n              <CalendarIcon className=\"me-2 size-4\" />\n              {displayValue ? (\n                <span>{displayValue}</span>\n              ) : (\n                <span>{placeholder}</span>\n              )}\n            </PopoverTrigger>\n            <PopoverContent className=\"p-0 w-auto\">\n              <div className=\"flex flex-col\">\n                <Calendar\n                  autoFocus\n                  {...calendarProps}\n                  mode=\"single\"\n                  selected={dateValue}\n                  onSelect={(selectedDate) => {\n                    if (!selectedDate) {\n                      field.onChange(undefined);\n                      return;\n                    }\n\n                    const nextDate = applyTimeToDate(\n                      selectedDate,\n                      timeValue || \"00:00:00\",\n                    );\n                    field.onChange(nextDate);\n                  }}\n                />\n                <div className=\"p-3 border-t\">\n                  <span className=\"block mb-2 text-muted-foreground text-xs\">\n                    Time\n                  </span>\n                  <Input\n                    id={`${field.name}-time`}\n                    name={`${field.name}-time`}\n                    type=\"time\"\n                    aria-invalid={!!fieldState.error}\n                    aria-required={required}\n                    aria-describedby={ariaDescribedBy}\n                    disabled={!dateValue}\n                    value={timeValue}\n                    onBlur={() => {\n                      field.onBlur();\n\n                      const normalizedValue = normalizeTimeValue(\n                        timeValue,\n                        showSeconds,\n                      );\n                      if (!normalizedValue) {\n                        field.onChange(undefined);\n                        return;\n                      }\n\n                      if (dateValue) {\n                        field.onChange(\n                          applyTimeToDate(dateValue, normalizedValue),\n                        );\n                      }\n                    }}\n                    onChange={(event) => {\n                      const nextInputValue = event.target.value;\n                      if (!nextInputValue) {\n                        field.onChange(undefined);\n                        return;\n                      }\n\n                      if (!dateValue) return;\n\n                      field.onChange(\n                        applyTimeToDate(dateValue, nextInputValue),\n                      );\n                    }}\n                    step={resolvedStep}\n                    className={timeInputClassName}\n                    {...restTimeInputProps}\n                  />\n                </div>\n              </div>\n            </PopoverContent>\n          </Popover>\n        );\n      }}\n    </BaseController>\n  );\n}\n"}]}