@utahdts/utah-design-system
Version:
Utah Design System React Library
169 lines (160 loc) • 6.4 kB
JSX
import { useCallback, useEffect, useRef } from 'react';
import { useImmer } from 'use-immer';
import { useGlobalKeyEvent } from '../../hooks/useGlobalKeyEvent';
import { joinClassNames } from '../../util/joinClassNames';
import { CalendarInput } from '../forms/CalendarInput/CalendarInput';
import { DateInput } from '../forms/DateInput';
import { Popup } from '../popups/Popup';
import { tableConstants } from './tableConstants';
/** @typedef { 'BEGIN' | 'END' } BeginEndDate */
/** @enum {BeginEndDate} */
const BeginEndDates = {
BEGIN: /** @type {BeginEndDate} */ ('BEGIN'),
END: /** @type {BeginEndDate} */ ('END'),
};
/**
* @param {BeginEndDate} whichInput
* @param {string} newValue
* @param {string | undefined} currentBeginDate
* @param {string | undefined} currentEndDate
* @returns {string}
*/
function formatNewValue(whichInput, newValue, currentBeginDate, currentEndDate) {
const beginDateStr = whichInput === BeginEndDates.BEGIN ? newValue : (currentBeginDate || '');
const endDateStr = whichInput === BeginEndDates.END ? newValue : (currentEndDate || '');
return `${beginDateStr}${tableConstants.dateFilterSeparator}${endDateStr}`;
}
/**
* @param {object} props
* @param {string} [props.dateFormat]
* @param {string} props.id
* @param {boolean} props.isPopupOpen
* @param {(newValue: string) => void} props.onChange
* @param {import('react').RefObject<HTMLDivElement | null>} props.popupReferenceElement
* @param {(isPopupOpen: boolean) => void} props.setIsPopupOpen
* @param {string} props.tableFilterDateId
* @param {string} props.value
* @returns {import('react').JSX.Element}
*/
export function TableFilterDateRangePopup({
dateFormat,
id,
isPopupOpen,
onChange,
popupReferenceElement,
setIsPopupOpen,
tableFilterDateId,
value,
}) {
const beginDateRef = useRef(/** @type {HTMLDivElement | null} */(null));
const [currentInput, setCurrentInput] = useImmer(/** @type {BeginEndDate} */(BeginEndDates.BEGIN));
const calendarInputRef = useRef(/** @type {HTMLDivElement | null} */(null));
useEffect(
() => {
// when popup first opens, focus on the begin date
if (isPopupOpen) {
const beginDateInput = beginDateRef.current?.querySelector('.date-input');
// @ts-expect-error
beginDateInput?.focus();
}
},
[isPopupOpen]
);
const [beginDateStr, endDateStr] = (value || '').split(tableConstants.dateFilterSeparator);
// close popup anytime the escape key is pressed
useGlobalKeyEvent({ whichKeyCode: 'Escape', onKeyUp: useCallback(() => setIsPopupOpen(false), []) });
const moveToCalendarInput = useCallback(
/** @param {React.KeyboardEvent<HTMLInputElement>} e */
(e) => {
// move to calendar input on key down
if (e.key === 'ArrowDown') {
e.preventDefault();
e.stopPropagation();
const focusOnThis = /** @type {HTMLElement | null} */ (calendarInputRef.current?.querySelector('.calendar-input__cell--focused'));
focusOnThis?.focus();
}
},
[]
);
return (
<Popup
ariaLabelledBy={tableFilterDateId}
className={joinClassNames(
'table-filter-date__popup',
!isPopupOpen && 'visually-hidden'
)}
hasCloseButton
id={id}
isVisible={isPopupOpen}
onVisibleChange={(_, isVisible) => setIsPopupOpen(isVisible)}
referenceElement={popupReferenceElement}
role="dialog"
>
<div className="flex gap-xs full-width">
<DateInput
ariaLabel="Date Filter Date Begin."
className="table-filter-date-popup__begin-date"
dateFormat={dateFormat}
hasCalendarPopup={false}
id={`table-filter-date-range-popup__${tableFilterDateId}__begin-date`}
innerRef={beginDateRef}
isClearable
label="Date Begin"
onChange={(newValue) => onChange(formatNewValue(BeginEndDates.BEGIN, newValue, beginDateStr, endDateStr))}
onClear={() => onChange(formatNewValue(BeginEndDates.BEGIN, '', beginDateStr, endDateStr))}
value={beginDateStr}
onKeyUp={moveToCalendarInput}
// @ts-expect-error
onFocus={() => setCurrentInput(BeginEndDates.BEGIN)}
/>
<DateInput
ariaLabel="Date Filter Date End."
className="table-filter-date-popup__end-date"
dateFormat={dateFormat}
hasCalendarPopup={false}
id={`table-filter-date-range-popup__${tableFilterDateId}__end-date`}
isClearable
label="Date End"
onChange={(newValue) => onChange(formatNewValue(BeginEndDates.END, newValue, beginDateStr, endDateStr))}
onClear={() => onChange(formatNewValue(BeginEndDates.END, '', beginDateStr, endDateStr))}
onKeyUp={moveToCalendarInput}
value={endDateStr}
// @ts-expect-error
onFocus={() => setCurrentInput(BeginEndDates.END)}
/>
</div>
<div>
<div className="table-filter-date-popup__selected-date-chiclets">
<div
className={joinClassNames(
'table-filter-date-popup__selected-date-chiclet',
currentInput === BeginEndDates.BEGIN
? 'table-filter-date-popup__selected-date-chiclet--selected'
: 'table-filter-date-popup__selected-date-chiclet--not-selected'
)}
/>
<div
className={joinClassNames(
'table-filter-date-popup__selected-date-chiclet',
currentInput === BeginEndDates.END
? 'table-filter-date-popup__selected-date-chiclet--selected'
: 'table-filter-date-popup__selected-date-chiclet--not-selected'
)}
/>
</div>
<hr />
<CalendarInput
className="table-filter-date-popup__calendar"
dateFormat={dateFormat}
id={`calendar-input__${tableFilterDateId}`}
innerRef={calendarInputRef}
label={`Calendar for table filter ${currentInput === BeginEndDates.BEGIN ? 'begin' : 'end'} date`}
labelClassName="visually-hidden"
onChange={(newValue) => onChange(formatNewValue(currentInput, newValue, beginDateStr, endDateStr))}
showTodayButton
value={(currentInput === BeginEndDates.BEGIN ? beginDateStr : endDateStr) ?? ''}
/>
</div>
</Popup>
);
}