diff --git a/src/components/InputTime/AsString.tsx b/src/components/InputTime/AsString.tsx index 60489e50..e629c4ce 100644 --- a/src/components/InputTime/AsString.tsx +++ b/src/components/InputTime/AsString.tsx @@ -29,6 +29,7 @@ import styles from './InputTime.module.css'; export interface AsStringProps extends Omit { + formatTime?: (date: Date) => string; fuzzyInputProps?: InputProps; max?: string; min?: string; @@ -40,6 +41,7 @@ export interface AsStringProps const AsString: React.FC = ({ className = '', disabled, + formatTime, forwardedRef, fuzzyInputProps = {}, max, @@ -101,7 +103,7 @@ const AsString: React.FC = ({ // Fuzzy value is user input. const [fuzzyValue, setFuzzyValue] = useState( - value ? getLocaleTimeStringFromShortTimeString(value) : '' + value ? getLocaleTimeStringFromShortTimeString(value, { formatTime }) : '' ); const syncValidity = ( @@ -142,16 +144,21 @@ const AsString: React.FC = ({ }; const handleBlurFuzzyValue = () => { - setFuzzyValue(value ? getLocaleTimeStringFromShortTimeString(value) : ''); + setFuzzyValue( + value ? getLocaleTimeStringFromShortTimeString(value, { formatTime }) : '' + ); }; const handleSelectMenuItem = (e: { target: { value: Date } }) => { const dateTime = e.target.value; - const label = dateTime.toLocaleTimeString([], { - hour: 'numeric', - minute: '2-digit', - }); + const label = + typeof formatTime === 'function' + ? formatTime(dateTime) + : dateTime.toLocaleTimeString([], { + hour: 'numeric', + minute: '2-digit', + }); const timeString = getShortTimeString( dateTime.getHours(), @@ -170,11 +177,15 @@ const AsString: React.FC = ({ } if (localRef?.current && document.activeElement !== localRef?.current) { - setFuzzyValue(value ? getLocaleTimeStringFromShortTimeString(value) : ''); + setFuzzyValue( + value + ? getLocaleTimeStringFromShortTimeString(value, { formatTime }) + : '' + ); } syncValidity(shadowTimeInputRef, localRef); - }, [localRef, shadowTimeInputRef, step, value]); + }, [formatTime, localRef, shadowTimeInputRef, step, value]); // Sync validity on min/max changes useEffect(() => { @@ -212,16 +223,17 @@ const AsString: React.FC = ({ /> {showDropdown && ( )} diff --git a/src/components/InputTime/Dropdown/Dropdown.tsx b/src/components/InputTime/Dropdown/Dropdown.tsx index e1746df3..50c5367a 100644 --- a/src/components/InputTime/Dropdown/Dropdown.tsx +++ b/src/components/InputTime/Dropdown/Dropdown.tsx @@ -22,6 +22,7 @@ import styles from './Dropdown.module.css'; interface DropdownProps { className?: string; disabled?: boolean; + formatTime?: (date: Date) => string; max?: string; min?: string; onSelectMenuItem: Function; @@ -36,6 +37,7 @@ interface DropdownProps { const Dropdown: React.FC = ({ className = '', disabled, + formatTime, max, min, onSelectMenuItem, @@ -94,10 +96,13 @@ const Dropdown: React.FC = ({ do { attempts += 1; - const label = current.toLocaleTimeString([], { - hour: 'numeric', - minute: '2-digit', - }); + const label = + typeof formatTime === 'function' + ? formatTime(current) + : current.toLocaleTimeString([], { + hour: 'numeric', + minute: '2-digit', + }); if ( (maxDate === undefined || current <= maxDate) && @@ -116,7 +121,16 @@ const Dropdown: React.FC = ({ } while (attempts <= maxAttempts && current < end); return options; - }, [customStep, max, min, value, onSelectMenuItem, showDropdown, stepFrom]); + }, [ + customStep, + formatTime, + max, + min, + onSelectMenuItem, + showDropdown, + stepFrom, + value, + ]); return ( { {}} step="2700" value="01:30" /> @@ -125,6 +126,40 @@ describe('InputTime', () => { }); }); + describe('when passed a custom `formatTime` prop', () => { + it('uses the custom formatter for the fuzzy input', () => { + const { getByLabelText } = render( + + `${date.getHours()}🎈:${date.getMinutes()}🐝` + } + fuzzyInputProps={{ 'aria-label': 'time input' }} + value="01:30" + /> + ); + + const baseInputTime = getByLabelText('time input') as HTMLInputElement; + expect(baseInputTime.value).toEqual('1🎈:30🐝'); + }); + + it('uses the custom formatter for the dropdown options', () => { + const { getByLabelText, queryByText } = render( + + `${date.getHours()}🎈:${date.getMinutes()}🐝` + } + toggleAriaLabel="Toggle" + /> + ); + + const toggle = getByLabelText('Toggle'); + // Open dropdown + userEvent.click(toggle); + const select10 = queryByText('10🎈:0🐝'); + expect(select10).toBeTruthy(); + }); + }); + describe('dropdown time picker', () => { it('clicking a time selects it', () => { const handleChange = jest.fn((e) => { diff --git a/src/components/InputTime/story.tsx b/src/components/InputTime/story.tsx index fa55e5bd..6810910a 100644 --- a/src/components/InputTime/story.tsx +++ b/src/components/InputTime/story.tsx @@ -191,6 +191,17 @@ storiesOf('Planets/InputTime', module) value={defaultValueDate} /> + + With a custom `formatTime` function + + `${date.getHours()}🎈:${date.getMinutes()}🐝` + } + onChange={action('onChange date')} + value={new Date().toISOString()} + /> + Requires you to pick a future date time { return date; }; -export const getLocaleTimeStringFromShortTimeString = (value: string) => - getDateTimeFromShortTimeString(value).toLocaleTimeString([], { - hour: 'numeric', - minute: '2-digit', - }); +export const getLocaleTimeStringFromShortTimeString = ( + value: string, + { formatTime } +) => + typeof formatTime === 'function' + ? formatTime(getDateTimeFromShortTimeString(value)) + : getDateTimeFromShortTimeString(value).toLocaleTimeString([], { + hour: 'numeric', + minute: '2-digit', + }); export const guessTimeFromString = (string: string) => { const invalidChars = new RegExp('[^\\d:\\spam.]', 'gi');