All Downloads are FREE. Search and download functionalities are using the official Maven repository.

package.src.components.DatePicker.DatePicker.tsx Maven / Gradle / Ivy

Go to download

This library provides a set of common React components for use with the PatternFly reference implementation.

The newest version!
import * as React from 'react';
import { css } from '@patternfly/react-styles';
import styles from '@patternfly/react-styles/css/components/DatePicker/date-picker';
import buttonStyles from '@patternfly/react-styles/css/components/Button/button';
import calendarMonthStyles from '@patternfly/react-styles/css/components/CalendarMonth/calendar-month';
import { TextInput, TextInputProps } from '../TextInput/TextInput';
import { Popover, PopoverProps } from '../Popover/Popover';
import { InputGroup, InputGroupItem } from '../InputGroup';
import OutlinedCalendarAltIcon from '@patternfly/react-icons/dist/esm/icons/outlined-calendar-alt-icon';
import { CalendarMonth, CalendarFormat } from '../CalendarMonth';
import { useImperativeHandle } from 'react';
import { KeyTypes } from '../../helpers';
import { isValidDate } from '../../helpers/datetimeUtils';
import { HelperText, HelperTextItem } from '../HelperText';
import cssFormControlWidthChars from '@patternfly/react-tokens/dist/esm/c_date_picker__input_c_form_control_width_chars';

/** Props that customize the requirement of a date */
export interface DatePickerRequiredObject {
  /** Flag indicating the date is required. */
  isRequired?: boolean;
  /** Error message to display when the text input is empty and the isRequired prop is also passed in. */
  emptyDateText?: string;
}

/** The main date picker component. */

export interface DatePickerProps
  extends CalendarFormat,
    Omit, 'onChange' | 'onFocus' | 'onBlur' | 'disabled' | 'ref'> {
  /** The container to append the menu to. Defaults to 'inline'.
   * If your menu is being cut off you can append it to an element higher up the DOM tree.
   * Some examples:
   * menuAppendTo={() => document.body};
   * menuAppendTo={document.getElementById('target')}
   */
  appendTo?: HTMLElement | ((ref?: HTMLElement) => HTMLElement) | 'inline';
  /** Accessible label for the date picker. */
  'aria-label'?: string;
  /** Accessible label for the button to open the date picker. */
  buttonAriaLabel?: string;
  /** Additional classes added to the date picker. */
  className?: string;
  /** How to format the date in the text input. */
  dateFormat?: (date: Date) => string;
  /** How to parse the date in the text input. */
  dateParse?: (value: string) => Date;
  /** Helper text to display alongside the date picker. Expects a HelperText component. */
  helperText?: React.ReactNode;
  /** Additional props for the text input. */
  inputProps?: TextInputProps;
  /** Flag indicating the date picker is disabled. */
  isDisabled?: boolean;
  /** Error message to display when the text input contains a non-empty value in an invalid format. */
  invalidFormatText?: string;
  /** Callback called every time the text input loses focus. */
  onBlur?: (event: any, value: string, date?: Date) => void;
  /** Callback called every time the text input value changes. */
  onChange?: (event: React.FormEvent, value: string, date?: Date) => void;
  /** String to display in the empty text input as a hint for the expected date format. */
  placeholder?: string;
  /** Props to pass to the popover that contains the calendar month component. */
  popoverProps?: Partial>;
  /** Options to customize the requirement of a date */
  requiredDateOptions?: DatePickerRequiredObject;
  /** Functions that returns an error message if a date is invalid. */
  validators?: ((date: Date) => string)[];
  /** Value of the text input. */
  value?: string;
}

/** Allows finer control over the calendar's open state when a React ref is passed into the
 * date picker component. Accessed via ref.current[property], e.g. ref.current.toggleCalendar().
 */

export interface DatePickerRef {
  /** Current calendar open status. */
  isCalendarOpen: boolean;
  /** Sets the calendar open status. */
  setCalendarOpen: (isOpen: boolean) => void;
  /** Toggles the calendar open status. If no parameters are passed, the calendar will simply
   * toggle its open status.
   * If the isOpen parameter is passed, that will set the calendar open status to the value
   * of the isOpen parameter.
   * If the eventKey parameter is set to 'Escape', that will invoke the date pickers
   * onEscapePress event to toggle the correct control appropriately.
   */
  toggleCalendar: (isOpen?: boolean) => void;
}

export const yyyyMMddFormat = (date: Date) =>
  `${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}-${date
    .getDate()
    .toString()
    .padStart(2, '0')}`;

const DatePickerBase = (
  {
    className,
    locale = undefined,
    dateFormat = yyyyMMddFormat,
    dateParse = (val: string) => (val.split('-').length === 3 ? new Date(`${val}T00:00:00`) : new Date(undefined)),
    isDisabled = false,
    placeholder = 'YYYY-MM-DD',
    value: valueProp = '',
    'aria-label': ariaLabel = 'Date picker',
    buttonAriaLabel = 'Toggle date picker',
    onChange = (): any => undefined,
    onBlur = (): any => undefined,
    invalidFormatText = 'Invalid date',
    requiredDateOptions,
    helperText,
    appendTo = 'inline',
    popoverProps,
    monthFormat,
    weekdayFormat,
    longWeekdayFormat,
    dayFormat,
    weekStart,
    validators = [],
    rangeStart,
    style: styleProps = {},
    inputProps = {},
    ...props
  }: DatePickerProps,
  ref: React.Ref
) => {
  const [value, setValue] = React.useState(valueProp);
  const [valueDate, setValueDate] = React.useState(dateParse(value));
  const [errorText, setErrorText] = React.useState('');
  const [popoverOpen, setPopoverOpen] = React.useState(false);
  const [selectOpen, setSelectOpen] = React.useState(false);
  const [pristine, setPristine] = React.useState(true);
  const [textInputFocused, setTextInputFocused] = React.useState(false);
  const widthChars = React.useMemo(() => Math.max(dateFormat(new Date()).length, placeholder.length), [dateFormat]);
  const style = { [cssFormControlWidthChars.name]: widthChars, ...styleProps };
  const buttonRef = React.useRef();
  const datePickerWrapperRef = React.useRef();
  const triggerRef = React.useRef();
  const dateIsRequired = requiredDateOptions?.isRequired || false;
  const emptyDateText = requiredDateOptions?.emptyDateText || 'Date cannot be blank';

  React.useEffect(() => {
    setValue(valueProp);
    setValueDate(dateParse(valueProp));
  }, [valueProp]);

  React.useEffect(() => {
    setPristine(!value);
    const newValueDate = dateParse(value);
    if (errorText && isValidDate(newValueDate)) {
      setError(newValueDate);
    }
    if (value === '' && !pristine && !textInputFocused) {
      dateIsRequired ? setErrorText(emptyDateText) : setErrorText('');
    }
  }, [value]);

  const setError = (date: Date) => {
    setErrorText(validators.map((validator) => validator(date)).join('\n') || '');
  };

  const onTextInput = (event: React.FormEvent, value: string) => {
    setValue(value);
    setErrorText('');
    const newValueDate = dateParse(value);
    setValueDate(newValueDate);
    if (isValidDate(newValueDate)) {
      onChange(event, value, new Date(newValueDate));
    } else {
      onChange(event, value);
    }
  };

  const onInputBlur = (event: any) => {
    setTextInputFocused(false);
    const newValueDate = dateParse(value);
    const dateIsValid = isValidDate(newValueDate);
    const onBlurDateArg = dateIsValid ? new Date(newValueDate) : undefined;
    onBlur(event, value, onBlurDateArg);

    if (dateIsValid) {
      setError(newValueDate);
    }

    if (!dateIsValid && !pristine) {
      setErrorText(invalidFormatText);
    }

    if (!dateIsValid && pristine && requiredDateOptions?.isRequired) {
      setErrorText(emptyDateText);
    }
  };

  const onDateClick = (_event: React.MouseEvent, newValueDate: Date) => {
    const newValue = dateFormat(newValueDate);
    setValue(newValue);
    setValueDate(newValueDate);
    setError(newValueDate);
    setPopoverOpen(false);
    onChange(null, newValue, new Date(newValueDate));
  };

  const onKeyPress = (ev: React.KeyboardEvent) => {
    if (ev.key === 'Enter' && value) {
      if (isValidDate(valueDate)) {
        setError(valueDate);
      } else {
        setErrorText(invalidFormatText);
      }
    }
  };

  useImperativeHandle(
    ref,
    () => ({
      setCalendarOpen: (isOpen: boolean) => setPopoverOpen(isOpen),
      toggleCalendar: (setOpen?: boolean) => {
        setPopoverOpen((prev) => (setOpen !== undefined ? setOpen : !prev));
      },
      isCalendarOpen: popoverOpen
    }),
    [setPopoverOpen, popoverOpen, selectOpen]
  );

  const createFocusSelectorString = (modifierClass: string) =>
    `.${calendarMonthStyles.calendarMonthDatesCell}.${modifierClass} .${calendarMonthStyles.calendarMonthDate}`;
  const focusSelectorForSelectedDate = createFocusSelectorString(calendarMonthStyles.modifiers.selected);
  const focusSelectorForUnselectedDate = createFocusSelectorString(calendarMonthStyles.modifiers.current);

  return (
    
(date: Date) => !validator(date))} onSelectToggle={(open) => setSelectOpen(open)} monthFormat={monthFormat} weekdayFormat={weekdayFormat} longWeekdayFormat={longWeekdayFormat} dayFormat={dayFormat} weekStart={weekStart} rangeStart={rangeStart} /> } showClose={false} isVisible={popoverOpen} shouldClose={(event, hideFunction) => { event = event as KeyboardEvent; if (event.key === KeyTypes.Escape && selectOpen) { event.stopPropagation(); setSelectOpen(false); return false; } // Let our button handle toggling if (buttonRef.current && buttonRef.current.contains(event.target as Node)) { return false; } if (popoverOpen) { event.stopPropagation(); setPopoverOpen(false); hideFunction(); // If datepicker is required and the popover is opened without the text input // first receiving focus, we want to validate that the text input is not blank upon // closing the popover requiredDateOptions?.isRequired && !value && setErrorText(emptyDateText); } if (event.key === KeyTypes.Escape && popoverOpen) { event.stopPropagation(); } return true; }} withFocusTrap hasNoPadding hasAutoWidth appendTo={appendTo} triggerRef={triggerRef} {...popoverProps} >
setTextInputFocused(true)} onKeyPress={onKeyPress} {...inputProps} />
{(errorText || helperText) && (
{errorText ? ( {errorText} ) : ( helperText )}
)}
); }; export const DatePicker = React.forwardRef(DatePickerBase); DatePicker.displayName = 'DatePicker';




© 2015 - 2024 Weber Informatics LLC | Privacy Policy