@indico-data/design-system 2.52.0 → 2.53.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/components/forms/date/datePicker/types.d.ts +3 -0
- package/lib/components/forms/input/Input.d.ts +2 -1
- package/lib/components/forms/timePicker/TimePicker.d.ts +7 -0
- package/lib/components/forms/timePicker/TimePicker.stories.d.ts +6 -0
- package/lib/components/forms/timePicker/__tests__/TimePicker.test.d.ts +1 -0
- package/lib/components/forms/timePicker/constants.d.ts +13 -0
- package/lib/components/forms/timePicker/helpers.d.ts +5 -0
- package/lib/components/forms/timePicker/index.d.ts +1 -0
- package/lib/index.css +120 -1
- package/lib/index.d.ts +13 -2
- package/lib/index.esm.css +120 -1
- package/lib/index.esm.js +151 -94
- package/lib/index.esm.js.map +1 -1
- package/lib/index.js +151 -93
- package/lib/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/forms/date/datePicker/DatePicker.stories.tsx +67 -4
- package/src/components/forms/date/datePicker/DatePicker.tsx +28 -2
- package/src/components/forms/date/datePicker/styles/DatePicker.scss +9 -0
- package/src/components/forms/date/datePicker/types.ts +3 -0
- package/src/components/forms/form/styles/Form.scss +1 -0
- package/src/components/forms/input/Input.tsx +5 -2
- package/src/components/forms/numberInput/NumberInput.tsx +1 -1
- package/src/components/forms/timePicker/TimePicker.mdx +39 -0
- package/src/components/forms/timePicker/TimePicker.stories.tsx +79 -0
- package/src/components/forms/timePicker/TimePicker.tsx +104 -0
- package/src/components/forms/timePicker/__tests__/TimePicker.test.tsx +26 -0
- package/src/components/forms/timePicker/constants.ts +21 -0
- package/src/components/forms/timePicker/helpers.ts +14 -0
- package/src/components/forms/timePicker/index.ts +1 -0
- package/src/components/forms/timePicker/styles/TimePicker.scss +51 -0
- package/src/index.ts +1 -0
- package/src/styles/index.scss +2 -1
package/package.json
CHANGED
|
@@ -6,6 +6,41 @@ const meta: Meta = {
|
|
|
6
6
|
title: 'Forms/DatePicker',
|
|
7
7
|
component: DatePicker,
|
|
8
8
|
argTypes: {
|
|
9
|
+
timeValue: {
|
|
10
|
+
control: 'text',
|
|
11
|
+
description: 'The time value to display in the date picker.',
|
|
12
|
+
table: {
|
|
13
|
+
category: 'Props',
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
onTimeChange: {
|
|
17
|
+
control: false,
|
|
18
|
+
description: 'Callback when the time changes.',
|
|
19
|
+
table: {
|
|
20
|
+
category: 'Callbacks',
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
hasTimePicker: {
|
|
24
|
+
control: 'boolean',
|
|
25
|
+
description: 'Whether to show the time picker.',
|
|
26
|
+
table: {
|
|
27
|
+
category: 'Props',
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
min: {
|
|
31
|
+
control: 'date',
|
|
32
|
+
description: 'The minimum date that can be selected.',
|
|
33
|
+
table: {
|
|
34
|
+
category: 'Props',
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
max: {
|
|
38
|
+
control: 'date',
|
|
39
|
+
description: 'The maximum date that can be selected.',
|
|
40
|
+
table: {
|
|
41
|
+
category: 'Props',
|
|
42
|
+
},
|
|
43
|
+
},
|
|
9
44
|
captionLayout: {
|
|
10
45
|
control: 'select',
|
|
11
46
|
options: ['dropdown', 'dropdown-months', 'dropdown-years', 'label'],
|
|
@@ -249,13 +284,26 @@ export const Default: Story = {
|
|
|
249
284
|
numberOfMonths: 1,
|
|
250
285
|
isRequired: false,
|
|
251
286
|
isDisabled: false,
|
|
287
|
+
hasTimePicker: true,
|
|
288
|
+
timeValue: '00:00',
|
|
252
289
|
},
|
|
253
290
|
render: (args) => {
|
|
254
|
-
const [{ selected }, updateArgs] = useArgs();
|
|
291
|
+
const [{ selected, timeValue }, updateArgs] = useArgs();
|
|
255
292
|
const handleSelect = (date: Date) => {
|
|
256
293
|
updateArgs({ selected: date });
|
|
257
294
|
};
|
|
258
|
-
|
|
295
|
+
const handleTimeChange = (time: string) => {
|
|
296
|
+
updateArgs({ timeValue: time });
|
|
297
|
+
};
|
|
298
|
+
return (
|
|
299
|
+
<DatePicker
|
|
300
|
+
{...args}
|
|
301
|
+
selected={selected}
|
|
302
|
+
onSelect={handleSelect}
|
|
303
|
+
timeValue={timeValue}
|
|
304
|
+
onTimeChange={handleTimeChange}
|
|
305
|
+
/>
|
|
306
|
+
);
|
|
259
307
|
},
|
|
260
308
|
};
|
|
261
309
|
|
|
@@ -265,6 +313,7 @@ export const Single: Story = {
|
|
|
265
313
|
isRequired: false,
|
|
266
314
|
isDisabled: false,
|
|
267
315
|
mode: 'single',
|
|
316
|
+
hasTimePicker: true,
|
|
268
317
|
},
|
|
269
318
|
render: (args) => {
|
|
270
319
|
const [{ selected }, updateArgs] = useArgs();
|
|
@@ -280,13 +329,26 @@ export const Range: Story = {
|
|
|
280
329
|
isRequired: false,
|
|
281
330
|
isDisabled: false,
|
|
282
331
|
mode: 'range',
|
|
332
|
+
hasTimePicker: true,
|
|
283
333
|
},
|
|
284
334
|
render: (args) => {
|
|
285
|
-
const [{ selected }, updateArgs] = useArgs();
|
|
335
|
+
const [{ selected, timeValue }, updateArgs] = useArgs();
|
|
336
|
+
|
|
286
337
|
const handleSelect = (date: Date) => {
|
|
287
338
|
updateArgs({ selected: date });
|
|
288
339
|
};
|
|
289
|
-
|
|
340
|
+
|
|
341
|
+
return (
|
|
342
|
+
<DatePicker
|
|
343
|
+
{...args}
|
|
344
|
+
selected={selected}
|
|
345
|
+
onTimeChange={() => {
|
|
346
|
+
console.log('hit time change');
|
|
347
|
+
}}
|
|
348
|
+
onSelect={handleSelect}
|
|
349
|
+
timeValue={timeValue}
|
|
350
|
+
/>
|
|
351
|
+
);
|
|
290
352
|
},
|
|
291
353
|
};
|
|
292
354
|
|
|
@@ -296,6 +358,7 @@ export const Multi: Story = {
|
|
|
296
358
|
isRequired: false,
|
|
297
359
|
isDisabled: false,
|
|
298
360
|
mode: 'multiple',
|
|
361
|
+
hasTimePicker: true,
|
|
299
362
|
},
|
|
300
363
|
render: (args) => {
|
|
301
364
|
const [{ selected }, updateArgs] = useArgs();
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React, { ChangeEventHandler, useState } from 'react';
|
|
2
2
|
import { DateRange, DayPicker, Mode, OnSelectHandler } from 'react-day-picker';
|
|
3
3
|
import 'react-day-picker/style.css';
|
|
4
4
|
|
|
5
5
|
import { DatePickerProps } from './types';
|
|
6
6
|
import { getCommonProps } from './contants';
|
|
7
|
+
import { TimePicker } from '../../timePicker/TimePicker';
|
|
8
|
+
import { Col, Row } from '../../../grid';
|
|
7
9
|
|
|
8
10
|
export const DatePicker = (props: DatePickerProps) => {
|
|
9
11
|
const {
|
|
@@ -11,6 +13,7 @@ export const DatePicker = (props: DatePickerProps) => {
|
|
|
11
13
|
className,
|
|
12
14
|
captionLayout = 'dropdown',
|
|
13
15
|
selected,
|
|
16
|
+
hasTimePicker = false,
|
|
14
17
|
id,
|
|
15
18
|
month,
|
|
16
19
|
defaultMonth,
|
|
@@ -23,9 +26,11 @@ export const DatePicker = (props: DatePickerProps) => {
|
|
|
23
26
|
weekStartsOn,
|
|
24
27
|
firstWeekContainsDate,
|
|
25
28
|
today,
|
|
29
|
+
timeValue,
|
|
26
30
|
isRequired,
|
|
27
31
|
min,
|
|
28
32
|
max,
|
|
33
|
+
onTimeChange,
|
|
29
34
|
onSelect,
|
|
30
35
|
onMonthChange,
|
|
31
36
|
onNextClick,
|
|
@@ -72,6 +77,27 @@ export const DatePicker = (props: DatePickerProps) => {
|
|
|
72
77
|
const modeProps = modeMap[mode];
|
|
73
78
|
const commonProps = getCommonProps(props);
|
|
74
79
|
|
|
80
|
+
const handleTimeChange = (time: string) => {
|
|
81
|
+
onTimeChange?.(time);
|
|
82
|
+
};
|
|
83
|
+
|
|
75
84
|
const finalProps = { ...commonProps, ...rest, ...modeProps };
|
|
76
|
-
|
|
85
|
+
|
|
86
|
+
return (
|
|
87
|
+
<>
|
|
88
|
+
{hasTimePicker && (
|
|
89
|
+
<div className="time-picker-wrapper">
|
|
90
|
+
<Row align="center">
|
|
91
|
+
<Col xs="content">
|
|
92
|
+
<p className="ma-0">Select Time</p>
|
|
93
|
+
</Col>
|
|
94
|
+
<Col>
|
|
95
|
+
<TimePicker timeValue={timeValue ?? ''} onTimeChange={handleTimeChange} />
|
|
96
|
+
</Col>
|
|
97
|
+
</Row>
|
|
98
|
+
</div>
|
|
99
|
+
)}
|
|
100
|
+
<DayPicker {...finalProps} />
|
|
101
|
+
</>
|
|
102
|
+
);
|
|
77
103
|
};
|
|
@@ -19,6 +19,8 @@ export interface DatePickerProps {
|
|
|
19
19
|
month?: Date;
|
|
20
20
|
defaultMonth?: Date;
|
|
21
21
|
startMonth?: Date | undefined;
|
|
22
|
+
hasTimePicker?: boolean;
|
|
23
|
+
timeValue?: string;
|
|
22
24
|
endMonth?: Date;
|
|
23
25
|
components?: Partial<CustomComponents>;
|
|
24
26
|
numberOfMonths?: number;
|
|
@@ -30,6 +32,7 @@ export interface DatePickerProps {
|
|
|
30
32
|
isRequired?: any;
|
|
31
33
|
min?: number;
|
|
32
34
|
max?: number;
|
|
35
|
+
onTimeChange?: (time: string) => void;
|
|
33
36
|
onMonthChange?: MonthChangeEventHandler;
|
|
34
37
|
onNextClick?: MonthChangeEventHandler;
|
|
35
38
|
onPrevClick?: MonthChangeEventHandler;
|
|
@@ -12,10 +12,11 @@ export interface BaseInputProps {
|
|
|
12
12
|
placeholder?: string;
|
|
13
13
|
isDisabled?: boolean;
|
|
14
14
|
readonly?: boolean;
|
|
15
|
-
onChange
|
|
15
|
+
onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
|
16
16
|
onBlur?: (e: React.FocusEvent<HTMLInputElement>) => void;
|
|
17
17
|
onKeyDown?: (e: React.KeyboardEvent<HTMLInputElement>) => void;
|
|
18
18
|
errorMessage?: string | undefined;
|
|
19
|
+
maxLength?: number;
|
|
19
20
|
helpText?: string;
|
|
20
21
|
iconName?: IconName;
|
|
21
22
|
isClearable?: boolean;
|
|
@@ -37,6 +38,7 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
|
|
37
38
|
isClearable,
|
|
38
39
|
errorMessage,
|
|
39
40
|
helpText,
|
|
41
|
+
maxLength,
|
|
40
42
|
iconName,
|
|
41
43
|
onChange,
|
|
42
44
|
onBlur,
|
|
@@ -48,7 +50,7 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
|
|
48
50
|
) => {
|
|
49
51
|
const hasErrors = errorMessage && errorMessage.length > 0;
|
|
50
52
|
const handleClear = () => {
|
|
51
|
-
onChange({ target: { value: '' } } as React.ChangeEvent<HTMLInputElement>);
|
|
53
|
+
onChange?.({ target: { value: '' } } as React.ChangeEvent<HTMLInputElement>);
|
|
52
54
|
};
|
|
53
55
|
|
|
54
56
|
const inputClasses = classNames(
|
|
@@ -77,6 +79,7 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
|
|
77
79
|
readOnly={readonly}
|
|
78
80
|
onChange={onChange}
|
|
79
81
|
onBlur={onBlur}
|
|
82
|
+
maxLength={maxLength}
|
|
80
83
|
onKeyDown={onKeyDown}
|
|
81
84
|
className={inputClasses}
|
|
82
85
|
aria-invalid={hasErrors ? true : undefined}
|
|
@@ -34,7 +34,7 @@ const NumberInput = React.forwardRef<HTMLInputElement, NumberInputProps>(
|
|
|
34
34
|
const hasErrors = errorMessage && errorMessage.length > 0;
|
|
35
35
|
|
|
36
36
|
const handleClear = () => {
|
|
37
|
-
onChange({ target: { value: '' } } as React.ChangeEvent<HTMLInputElement>);
|
|
37
|
+
onChange?.({ target: { value: '' } } as React.ChangeEvent<HTMLInputElement>);
|
|
38
38
|
};
|
|
39
39
|
|
|
40
40
|
const inputClasses = classNames(
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { Canvas, Meta, Controls } from '@storybook/blocks';
|
|
2
|
+
import * as TimePicker from './TimePicker.stories';
|
|
3
|
+
import { Row, Col } from '../../grid/index';
|
|
4
|
+
|
|
5
|
+
<Meta title="Forms/TimePicker" name="TimePicker" />
|
|
6
|
+
|
|
7
|
+
# TimePicker
|
|
8
|
+
The time picker component may be used as a stand alone time selector or as part of a date picker component.
|
|
9
|
+
<Canvas of={TimePicker.Default}
|
|
10
|
+
source={{
|
|
11
|
+
code: `
|
|
12
|
+
const handleInputChange = (value: any) => {
|
|
13
|
+
setTime(value.time + ' ' + value.period);
|
|
14
|
+
};
|
|
15
|
+
const [time, setTime] = useState('');
|
|
16
|
+
return
|
|
17
|
+
<Row>
|
|
18
|
+
<Col xs={12} className="mb-4">
|
|
19
|
+
<h1>Time Picker</h1>
|
|
20
|
+
<p>Time Value returned: {time}</p>
|
|
21
|
+
</Col>
|
|
22
|
+
<Col xs={12}>
|
|
23
|
+
<TimePicker
|
|
24
|
+
onInputChange={handleInputChange}
|
|
25
|
+
defaultValue={{
|
|
26
|
+
time: '12:00',
|
|
27
|
+
period: 'AM'
|
|
28
|
+
}}
|
|
29
|
+
/>
|
|
30
|
+
</Col>
|
|
31
|
+
</Row>
|
|
32
|
+
`,
|
|
33
|
+
}}
|
|
34
|
+
/>
|
|
35
|
+
|
|
36
|
+
<Controls of={TimePicker.Default} />
|
|
37
|
+
|
|
38
|
+
## Usage
|
|
39
|
+
The goal for this component is to return an object you can then parse and format as needed. It will automatically format the input to a human readable value. It will validate the input and display an error message if the value is incorrect.
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { TimePicker } from './TimePicker';
|
|
2
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
3
|
+
import { useArgs } from '@storybook/preview-api';
|
|
4
|
+
import { Col, Row } from '../../grid';
|
|
5
|
+
|
|
6
|
+
const meta: Meta<typeof TimePicker> = {
|
|
7
|
+
title: 'Forms/TimePicker',
|
|
8
|
+
component: TimePicker,
|
|
9
|
+
argTypes: {
|
|
10
|
+
onTimeChange: {
|
|
11
|
+
control: false,
|
|
12
|
+
description: 'returns an object with the time and errors if they exist',
|
|
13
|
+
table: {
|
|
14
|
+
category: 'Callbacks',
|
|
15
|
+
type: {
|
|
16
|
+
summary: '{ time: string, period: string, errors: Record<string, string> }',
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
timeValue: {
|
|
21
|
+
control: 'text',
|
|
22
|
+
description: 'The time value to be displayed in the input field',
|
|
23
|
+
defaultValue: '12:00 AM',
|
|
24
|
+
table: {
|
|
25
|
+
category: 'Props',
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
label: {
|
|
29
|
+
table: {
|
|
30
|
+
category: 'Props',
|
|
31
|
+
},
|
|
32
|
+
control: 'text',
|
|
33
|
+
description: 'The label for the time picker',
|
|
34
|
+
defaultValue: 'Time Picker',
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
decorators: [
|
|
38
|
+
(Story: React.ComponentType) => (
|
|
39
|
+
<div
|
|
40
|
+
style={{
|
|
41
|
+
height: '500px',
|
|
42
|
+
}}
|
|
43
|
+
>
|
|
44
|
+
<Story />
|
|
45
|
+
</div>
|
|
46
|
+
),
|
|
47
|
+
],
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export default meta;
|
|
51
|
+
|
|
52
|
+
type Story = StoryObj<typeof TimePicker>;
|
|
53
|
+
|
|
54
|
+
export const Default: Story = {
|
|
55
|
+
args: {
|
|
56
|
+
timeValue: '12:00 AM',
|
|
57
|
+
label: 'Time Picker',
|
|
58
|
+
},
|
|
59
|
+
|
|
60
|
+
render: (args) => {
|
|
61
|
+
const [{ timeValue }, updateArgs] = useArgs();
|
|
62
|
+
const handleTimeChange = (time: string) => {
|
|
63
|
+
updateArgs({ timeValue: time });
|
|
64
|
+
};
|
|
65
|
+
return (
|
|
66
|
+
<div>
|
|
67
|
+
<Row>
|
|
68
|
+
<Col xs={12} className="mb-4">
|
|
69
|
+
<h1>Time Picker</h1>
|
|
70
|
+
<p>Time Value returned: {timeValue}</p>
|
|
71
|
+
</Col>
|
|
72
|
+
<Col xs={12}>
|
|
73
|
+
<TimePicker {...args} onTimeChange={handleTimeChange} timeValue={timeValue} />
|
|
74
|
+
</Col>
|
|
75
|
+
</Row>
|
|
76
|
+
</div>
|
|
77
|
+
);
|
|
78
|
+
},
|
|
79
|
+
};
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import { Select } from '../select/Select';
|
|
3
|
+
import { Input } from '../input/Input';
|
|
4
|
+
import { Col, Row } from '../../grid';
|
|
5
|
+
import { FloatUI } from '../..';
|
|
6
|
+
import { hourOptions, minuteOptions, periodOptions } from './constants';
|
|
7
|
+
import { parseTimeValue } from './helpers';
|
|
8
|
+
import { SelectOption } from '../select/types';
|
|
9
|
+
import { SingleValue, MultiValue } from 'react-select';
|
|
10
|
+
|
|
11
|
+
interface TimePickerProps {
|
|
12
|
+
timeValue?: string;
|
|
13
|
+
label?: string;
|
|
14
|
+
onTimeChange?: (time: string) => void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const TimePicker = ({
|
|
18
|
+
timeValue = '',
|
|
19
|
+
label = 'Time Picker',
|
|
20
|
+
onTimeChange,
|
|
21
|
+
}: TimePickerProps) => {
|
|
22
|
+
const initialTime = parseTimeValue(timeValue);
|
|
23
|
+
const [hours, setHours] = useState(initialTime.hours);
|
|
24
|
+
const [minutes, setMinutes] = useState(initialTime.minutes);
|
|
25
|
+
const [period, setPeriod] = useState(initialTime.period);
|
|
26
|
+
|
|
27
|
+
const handleHourChange = (option: SingleValue<SelectOption>) => {
|
|
28
|
+
setHours(option?.value || '12');
|
|
29
|
+
onTimeChange?.(`${option?.value || '12'}:${minutes} ${period.toUpperCase()}`);
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const handleMinuteChange = (option: SelectOption) => {
|
|
33
|
+
setMinutes(option?.value || '00');
|
|
34
|
+
onTimeChange?.(`${hours}:${option?.value || '00'} ${period.toUpperCase()}`);
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const handlePeriodChange = (option: SelectOption) => {
|
|
38
|
+
setPeriod(option?.value || 'am');
|
|
39
|
+
onTimeChange?.(`${hours}:${minutes} ${(option?.value || 'am').toUpperCase()}`);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<div className="time-input-wrapper">
|
|
44
|
+
<FloatUI ariaLabel={label}>
|
|
45
|
+
<Input
|
|
46
|
+
data-testid="time-picker-input"
|
|
47
|
+
name="hours"
|
|
48
|
+
label={label}
|
|
49
|
+
hasHiddenLabel
|
|
50
|
+
value={`${hours}:${minutes} ${period.toUpperCase()}`}
|
|
51
|
+
readonly
|
|
52
|
+
className="time-picker-input"
|
|
53
|
+
/>
|
|
54
|
+
<Row gutterWidth={0} className="time-picker-row">
|
|
55
|
+
<Col xs="content">
|
|
56
|
+
<Select
|
|
57
|
+
menuIsOpen
|
|
58
|
+
className="hour-menu"
|
|
59
|
+
components={{ DropdownIndicator: () => null, IndicatorSeparator: () => null }}
|
|
60
|
+
name="hours"
|
|
61
|
+
value={{ label: hours, value: hours }}
|
|
62
|
+
onChange={
|
|
63
|
+
handleHourChange as (
|
|
64
|
+
newValue: SingleValue<SelectOption> | MultiValue<SelectOption>,
|
|
65
|
+
) => void
|
|
66
|
+
}
|
|
67
|
+
options={hourOptions}
|
|
68
|
+
/>
|
|
69
|
+
</Col>
|
|
70
|
+
<Col xs="content">
|
|
71
|
+
<Select
|
|
72
|
+
className="minute-menu"
|
|
73
|
+
components={{ DropdownIndicator: () => null, IndicatorSeparator: () => null }}
|
|
74
|
+
name="minutes"
|
|
75
|
+
options={minuteOptions}
|
|
76
|
+
menuIsOpen
|
|
77
|
+
value={{ label: minutes.padStart(2, '0'), value: minutes }}
|
|
78
|
+
onChange={
|
|
79
|
+
handleMinuteChange as (
|
|
80
|
+
newValue: SingleValue<SelectOption> | MultiValue<SelectOption>,
|
|
81
|
+
) => void
|
|
82
|
+
}
|
|
83
|
+
/>
|
|
84
|
+
</Col>
|
|
85
|
+
<Col xs="content">
|
|
86
|
+
<Select
|
|
87
|
+
menuIsOpen
|
|
88
|
+
className="period-menu"
|
|
89
|
+
components={{ DropdownIndicator: () => null, IndicatorSeparator: () => null }}
|
|
90
|
+
name="period"
|
|
91
|
+
options={periodOptions}
|
|
92
|
+
value={{ label: period.toUpperCase(), value: period }}
|
|
93
|
+
onChange={
|
|
94
|
+
handlePeriodChange as (
|
|
95
|
+
newValue: SingleValue<SelectOption> | MultiValue<SelectOption>,
|
|
96
|
+
) => void
|
|
97
|
+
}
|
|
98
|
+
/>
|
|
99
|
+
</Col>
|
|
100
|
+
</Row>
|
|
101
|
+
</FloatUI>
|
|
102
|
+
</div>
|
|
103
|
+
);
|
|
104
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { render, screen } from '@testing-library/react';
|
|
2
|
+
import { TimePicker } from '@/components/forms/timePicker/TimePicker';
|
|
3
|
+
import userEvent from '@testing-library/user-event';
|
|
4
|
+
|
|
5
|
+
describe('TimePicker', () => {
|
|
6
|
+
it('updates the time when the hour is changed', async () => {
|
|
7
|
+
render(<TimePicker />);
|
|
8
|
+
const timePickerInput = screen.getByTestId('time-picker-input');
|
|
9
|
+
await userEvent.click(timePickerInput);
|
|
10
|
+
|
|
11
|
+
const hourOption = screen.getByRole('option', { name: '1' });
|
|
12
|
+
await userEvent.click(hourOption);
|
|
13
|
+
|
|
14
|
+
expect(timePickerInput).toHaveValue('1:00 AM');
|
|
15
|
+
|
|
16
|
+
const minuteOption = screen.getByRole('option', { name: '01' });
|
|
17
|
+
await userEvent.click(minuteOption);
|
|
18
|
+
|
|
19
|
+
expect(timePickerInput).toHaveValue('1:01 AM');
|
|
20
|
+
|
|
21
|
+
const periodOption = screen.getByRole('option', { name: 'PM' });
|
|
22
|
+
await userEvent.click(periodOption);
|
|
23
|
+
|
|
24
|
+
expect(timePickerInput).toHaveValue('1:01 PM');
|
|
25
|
+
});
|
|
26
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
const hourOptions = Array.from({ length: 12 }, (_, i) => {
|
|
2
|
+
const num = `${i + 1}`;
|
|
3
|
+
return {
|
|
4
|
+
label: num,
|
|
5
|
+
value: num,
|
|
6
|
+
};
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
const minuteOptions = Array.from({ length: 60 }, (_, i) => {
|
|
10
|
+
const num = `${i}`.padStart(2, '0');
|
|
11
|
+
return {
|
|
12
|
+
label: num,
|
|
13
|
+
value: num,
|
|
14
|
+
};
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
const periodOptions = [
|
|
18
|
+
{ label: 'AM', value: 'am' },
|
|
19
|
+
{ label: 'PM', value: 'pm' },
|
|
20
|
+
];
|
|
21
|
+
export { hourOptions, minuteOptions, periodOptions };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// Parse initial time value
|
|
2
|
+
export const parseTimeValue = (time: string) => {
|
|
3
|
+
if (!time) return { hours: '12', minutes: '00', period: 'am' };
|
|
4
|
+
|
|
5
|
+
const match = time.match(/(\d{1,2}):(\d{2})\s*(am|pm)/i);
|
|
6
|
+
if (!match) return { hours: '12', minutes: '00', period: 'am' };
|
|
7
|
+
|
|
8
|
+
const [, hours, minutes, period] = match;
|
|
9
|
+
return {
|
|
10
|
+
hours: hours,
|
|
11
|
+
minutes: minutes,
|
|
12
|
+
period: period.toLowerCase(),
|
|
13
|
+
};
|
|
14
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './TimePicker';
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
.time-picker-input {
|
|
2
|
+
cursor: pointer;
|
|
3
|
+
}
|
|
4
|
+
.time-picker-row {
|
|
5
|
+
width: 150px;
|
|
6
|
+
|
|
7
|
+
.select__value-container {
|
|
8
|
+
text-align: center;
|
|
9
|
+
cursor: pointer;
|
|
10
|
+
}
|
|
11
|
+
.select-wrapper {
|
|
12
|
+
width: 50px;
|
|
13
|
+
.select__items {
|
|
14
|
+
justify-content: center;
|
|
15
|
+
}
|
|
16
|
+
.select__menu {
|
|
17
|
+
height: 230px;
|
|
18
|
+
padding-bottom: 0;
|
|
19
|
+
margin-bottom: 0;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
.select__menu-list {
|
|
23
|
+
max-height: 100%;
|
|
24
|
+
scrollbar-width: none; /* Firefox */
|
|
25
|
+
-ms-overflow-style: none; /* IE and Edge */
|
|
26
|
+
&::-webkit-scrollbar {
|
|
27
|
+
display: none; /* Chrome, Safari and Opera */
|
|
28
|
+
}
|
|
29
|
+
overflow-y: scroll;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.hour-menu {
|
|
33
|
+
.select__menu {
|
|
34
|
+
border-top-right-radius: 0;
|
|
35
|
+
border-bottom-right-radius: 0;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
.minute-menu {
|
|
39
|
+
.select__menu {
|
|
40
|
+
border-radius: 0;
|
|
41
|
+
border-right: none;
|
|
42
|
+
border-left: none;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
.period-menu {
|
|
46
|
+
.select__menu {
|
|
47
|
+
border-top-left-radius: 0;
|
|
48
|
+
border-bottom-left-radius: 0;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -18,6 +18,7 @@ export { Textarea } from './components/forms/textarea';
|
|
|
18
18
|
export { PasswordInput } from './components/forms/passwordInput';
|
|
19
19
|
export { Select as SelectInput } from './components/forms/select';
|
|
20
20
|
export { DatePicker } from './components/forms/date/datePicker/DatePicker';
|
|
21
|
+
export { TimePicker } from './components/forms/timePicker';
|
|
21
22
|
export { IconTriggerDatePicker } from './components/forms/date/iconTriggerDatePicker';
|
|
22
23
|
export { SingleInputDatePicker } from './components/forms/date/inputDatePicker';
|
|
23
24
|
export { Form } from './components/forms/form';
|
package/src/styles/index.scss
CHANGED
|
@@ -21,11 +21,12 @@
|
|
|
21
21
|
@import '../components/menu/styles/Menu.scss';
|
|
22
22
|
@import '../components/floatUI/styles/FloatUI.scss';
|
|
23
23
|
@import '../components/forms/date/datePicker/styles/DatePicker.scss';
|
|
24
|
+
@import '../components/forms/timePicker/styles/TimePicker.scss';
|
|
24
25
|
@import '../components/badge/styles/Badge.scss';
|
|
25
26
|
@import '../components/modal/styles/Modal.scss';
|
|
26
27
|
@import '../components/pagination/styles/Pagination.scss';
|
|
27
28
|
@import '../components/tanstackTable/styles/table.scss';
|
|
28
|
-
|
|
29
|
+
@import '../components/forms/timePicker/styles/TimePicker.scss';
|
|
29
30
|
@import '../components/pill/styles/Pill.scss';
|
|
30
31
|
@import 'sheets'; // Port to an sheets component when we build it
|
|
31
32
|
@import 'typography';
|