Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

InputTime takes formatTime for custom i18n time string formatting #237

Merged
merged 3 commits into from
Jul 23, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 22 additions & 10 deletions src/components/InputTime/AsString.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import styles from './InputTime.module.css';

export interface AsStringProps
extends Omit<InputProps, 'value' | 'max' | 'min'> {
formatTime?: (date: Date) => string;
fuzzyInputProps?: InputProps;
max?: string;
min?: string;
Expand All @@ -40,6 +41,7 @@ export interface AsStringProps
const AsString: React.FC<AsStringProps> = ({
className = '',
disabled,
formatTime,
forwardedRef,
fuzzyInputProps = {},
max,
Expand Down Expand Up @@ -101,7 +103,7 @@ const AsString: React.FC<AsStringProps> = ({

// Fuzzy value is user input.
const [fuzzyValue, setFuzzyValue] = useState(
value ? getLocaleTimeStringFromShortTimeString(value) : ''
value ? getLocaleTimeStringFromShortTimeString(value, { formatTime }) : ''
);

const syncValidity = (
Expand Down Expand Up @@ -142,16 +144,21 @@ const AsString: React.FC<AsStringProps> = ({
};

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(),
Expand All @@ -170,11 +177,15 @@ const AsString: React.FC<AsStringProps> = ({
}

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(() => {
Expand Down Expand Up @@ -212,16 +223,17 @@ const AsString: React.FC<AsStringProps> = ({
/>
{showDropdown && (
<Dropdown
toggleAriaLabel={toggleAriaLabel}
className={styles.addons}
disabled={disabled}
formatTime={formatTime}
max={max}
min={min}
value={value}
onSelectMenuItem={handleSelectMenuItem}
showDropdown={showDropdown}
step={dropdownStep}
stepFrom={stepFrom}
toggleAriaLabel={toggleAriaLabel}
value={value}
/>
)}
</div>
Expand Down
24 changes: 19 additions & 5 deletions src/components/InputTime/Dropdown/Dropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -36,6 +37,7 @@ interface DropdownProps {
const Dropdown: React.FC<DropdownProps> = ({
className = '',
disabled,
formatTime,
max,
min,
onSelectMenuItem,
Expand Down Expand Up @@ -94,10 +96,13 @@ const Dropdown: React.FC<DropdownProps> = ({
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) &&
Expand All @@ -116,7 +121,16 @@ const Dropdown: React.FC<DropdownProps> = ({
} while (attempts <= maxAttempts && current < end);

return options;
}, [customStep, max, min, value, onSelectMenuItem, showDropdown, stepFrom]);
}, [
customStep,
formatTime,
max,
min,
onSelectMenuItem,
showDropdown,
stepFrom,
value,
]);

return (
<EasyDropdown
Expand Down
35 changes: 35 additions & 0 deletions src/components/InputTime/InputTime.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ describe('InputTime', () => {
<input
data-testid="InputTime test"
min="01:30"
onChange={() => {}}
step="2700"
value="01:30"
/>
Expand Down Expand Up @@ -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(
<InputTime
formatTime={(date: Date) =>
`${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(
<InputTime
formatTime={(date: Date) =>
`${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) => {
Expand Down
11 changes: 11 additions & 0 deletions src/components/InputTime/story.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,17 @@ storiesOf('Planets/InputTime', module)
value={defaultValueDate}
/>
</Wrap>
<Wrap>
<Title>With a custom `formatTime` function</Title>
<InteractiveInput
Component={InputTime}
formatTime={(date: Date) =>
`${date.getHours()}🎈:${date.getMinutes()}🐝`
}
onChange={action('onChange date')}
value={new Date().toISOString()}
/>
</Wrap>
<Wrap>
<Title>Requires you to pick a future date time</Title>
<InteractiveInput
Expand Down
15 changes: 10 additions & 5 deletions src/components/InputTime/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,16 @@ export const getDateTimeFromShortTimeString = (value: string) => {
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');
Expand Down