Skip to content

Commit 26278f4

Browse files
author
karabij
committed
✨(frontend) add formik time input component
Add a component which enables the user to select a time and give it to formik context, with a suggestions dropdown
1 parent 303d7e9 commit 26278f4

File tree

7 files changed

+83246
-14145
lines changed

7 files changed

+83246
-14145
lines changed

src/frontend/magnify/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
"@testing-library/user-event": "14.2.1",
3838
"@types/jest": "28.1.1",
3939
"@types/react": "18.0.12",
40+
"@types/react-time-picker": "^4.0.2",
4041
"@types/styled-components": "5.1.25",
4142
"@types/validator": "13.7.3",
4243
"@typescript-eslint/eslint-plugin": "5.36.2",
@@ -59,6 +60,7 @@
5960
"msw": "0.47.4",
6061
"postcss": "8.4.14",
6162
"prettier": "2.7.0",
63+
"react-time-picker": "^5.1.0",
6264
"rollup": "2.75.6",
6365
"rollup-plugin-dts": "4.2.2",
6466
"rollup-plugin-peer-deps-external": "2.2.4",
@@ -97,7 +99,7 @@
9799
},
98100
"dependencies": {
99101
"@tanstack/react-query-devtools": "^4.8.0",
100-
"react-router-dom": "6.4.1",
102+
"react-router-dom": "6.3.0",
101103
"use-debounce": "8.0.4"
102104
}
103105
}

src/frontend/magnify/src/components/design-system/Formik/FormikDatePicker/FormikDatePicker.tsx

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ const messages = defineMessages({
1010
invalidStartingAt: {
1111
defaultMessage: 'Input date is not valid: Starting date should be set in the future.',
1212
description: 'Error message when event scheduling date time update is in the past.',
13-
id: 'components.design-system.Formik.invalidStartingAt',
13+
id: 'components.design-system.Formik.FormikDatePicker.invalidStartingAt',
1414
},
1515
});
1616

@@ -26,6 +26,9 @@ const customDateInputTheme = (theme: ThemeType) => {
2626
day: { extend: () => `border-radius: 2em; color: brand` },
2727
extend: `border-radius: 1em; padding: 1em`,
2828
},
29+
maskedInput: {
30+
extend: `font-family: roboto`,
31+
},
2932
};
3033
};
3134

@@ -34,16 +37,17 @@ export interface formikDatePickerProps {
3437
onChange: (date: string) => void;
3538
}
3639

40+
const nextYear = new Date();
41+
nextYear.setFullYear(new Date().getFullYear() + 1);
42+
const today = new Date();
43+
today.setHours(0, 0, 0, 0);
44+
3745
const FormikDatePicker: FunctionComponent<formikDatePickerProps> = ({ ...props }) => {
3846
const [field] = useField(props.name);
3947
const [startingAtError, setStartingAtError] = useState(false);
4048

4149
const formikContext = useFormikContext();
42-
const nextYear = new Date();
43-
nextYear.setFullYear(new Date().getFullYear() + 1);
4450
const intl = useIntl();
45-
const today = new Date();
46-
today.setHours(0, 0, 0, 0);
4751

4852
const theme = useTheme();
4953
const onDateChange = (event: { value: string | string[] }) => {
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import withFormik from '@bbbtech/storybook-formik';
2+
import { ComponentMeta, ComponentStory } from '@storybook/react';
3+
import React from 'react';
4+
import FormikTimePicker from './FormikTimePicker';
5+
6+
export default {
7+
title: 'Formik/TimePicker',
8+
component: FormikTimePicker,
9+
decorators: [withFormik],
10+
} as ComponentMeta<typeof FormikTimePicker>;
11+
12+
const Template: ComponentStory<typeof FormikTimePicker> = (args, context) => (
13+
<div>
14+
{context.parameters.title}
15+
<FormikTimePicker {...args} />
16+
</div>
17+
);
18+
19+
export const basicTimePicker = Template.bind({});
20+
21+
basicTimePicker.args = {
22+
name: 'time',
23+
isToday: true,
24+
};
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
/* eslint-disable react/no-unknown-property */
2+
import { useField, useFormikContext } from 'formik';
3+
import { Box, DropButton, Button, Text } from 'grommet';
4+
import { Alert } from 'grommet-icons';
5+
import { normalizeColor } from 'grommet/utils';
6+
import React, { FunctionComponent, ReactElement, useState } from 'react';
7+
import { defineMessages, useIntl } from 'react-intl';
8+
import TimePicker, { TimePickerValue } from 'react-time-picker';
9+
import { useTheme } from 'styled-components';
10+
11+
const messages = defineMessages({
12+
invalidTime: {
13+
defaultMessage: 'Input time is not valid: it should be set in the future.',
14+
description: 'Error message when event scheduling time update is in the past.',
15+
id: 'components.design-system.Formik.FormikTimePicker.invalidTime',
16+
},
17+
});
18+
19+
export interface FormikTimePickerProps {
20+
name: string;
21+
isToday: boolean;
22+
onChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
23+
}
24+
25+
const computeSuggestions = (): string[] => {
26+
let suggestions = ['00:00'];
27+
let minutes: string = '00';
28+
let hours: string = '00';
29+
30+
while (`${hours}:${minutes}` < '23:45') {
31+
console.log(`${hours}:${minutes}`);
32+
if (minutes == '45') {
33+
hours = +hours >= 9 ? (+hours + 1).toString() : `0${(+hours + 1).toString()}`;
34+
minutes = '00';
35+
} else {
36+
minutes = (+minutes + 15).toString();
37+
}
38+
suggestions.push(`${hours}:${minutes}`);
39+
}
40+
return suggestions;
41+
};
42+
43+
const allSuggestions = computeSuggestions();
44+
45+
interface suggestionButtonProps {
46+
buttonValue: string;
47+
choiceValue: string;
48+
onClick: (value: string) => void;
49+
}
50+
51+
const SuggestionButton: FunctionComponent<suggestionButtonProps> = ({ ...props }): ReactElement => {
52+
const isChosenButton: boolean = props.buttonValue == props.choiceValue;
53+
const theme = useTheme();
54+
return (
55+
<Button
56+
color={isChosenButton ? `${normalizeColor('light-2', theme)}` : 'black'}
57+
fill={isChosenButton ? 'horizontal' : false}
58+
justify="center"
59+
margin={{ top: 'xsmall' }}
60+
primary={isChosenButton}
61+
onClick={() => {
62+
props.onClick(props.buttonValue);
63+
}}
64+
>
65+
<Box alignContent="center" alignSelf="center">
66+
<Text color="black" textAlign="center">
67+
{props.buttonValue}
68+
</Text>
69+
</Box>
70+
</Button>
71+
);
72+
};
73+
74+
const FormikTimePicker: FunctionComponent<FormikTimePickerProps> = ({ ...props }) => {
75+
const [field] = useField(props.name);
76+
const formikContext = useFormikContext();
77+
const [open, setOpen] = useState<boolean | undefined>(undefined);
78+
const [timeError, setTimeError] = useState(false);
79+
const intl = useIntl();
80+
81+
const onTimeChange = (value: TimePickerValue | string) => {
82+
const today = new Date();
83+
if (props.isToday && value < `${today.getHours()}:${today.getMinutes()}`) {
84+
setTimeError(true);
85+
} else {
86+
setTimeError(false);
87+
formikContext.setFieldValue(props.name, value);
88+
}
89+
};
90+
91+
const onTimeSelectChange = (value: string) => {
92+
onTimeChange(value);
93+
setOpen(false);
94+
};
95+
96+
const suggestionButtons = allSuggestions.map((value: string) => (
97+
<SuggestionButton
98+
key={value}
99+
buttonValue={value}
100+
choiceValue={field.value}
101+
onClick={onTimeSelectChange}
102+
/>
103+
));
104+
105+
return (
106+
<Box align="start" pad="small">
107+
<DropButton
108+
dropAlign={{ top: 'bottom' }}
109+
onClose={() => setOpen(false)}
110+
open={open}
111+
dropContent={
112+
<Box align="center" basis="small" direction="column" gap="5px">
113+
{suggestionButtons}
114+
</Box>
115+
}
116+
onOpen={() => {
117+
setOpen(true);
118+
}}
119+
>
120+
<TimePicker disableClock onChange={onTimeChange} value={field.value}></TimePicker>
121+
</DropButton>
122+
{timeError && (
123+
<Box
124+
align="center"
125+
background="#ffcccb"
126+
direction="row"
127+
gap="8px"
128+
margin={{ top: 'small' }}
129+
pad="8px"
130+
round="small"
131+
>
132+
<Alert color="status-critical" size="medium" />
133+
<Text color="status-critical" size="small">
134+
{intl.formatMessage(messages.invalidTime)}
135+
</Text>
136+
</Box>
137+
)}
138+
</Box>
139+
);
140+
};
141+
142+
export default FormikTimePicker;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { default, FormikTimePickerProps } from './FormikTimePicker';

0 commit comments

Comments
 (0)