@indico-data/design-system 2.54.0 → 2.55.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.
Files changed (34) hide show
  1. package/lib/components/forms/date/inputDateTimePicker/SingleInputDateTimePicker.d.ts +24 -0
  2. package/lib/components/forms/date/inputDateTimePicker/SingleInputDateTimePicker.stories.d.ts +6 -0
  3. package/lib/components/forms/date/inputDateTimePicker/helpers.d.ts +1 -0
  4. package/lib/components/forms/date/inputDateTimePicker/index.d.ts +1 -0
  5. package/lib/components/forms/subcomponents/DisplayFormError.d.ts +2 -1
  6. package/lib/components/forms/timePicker/TimePicker.d.ts +3 -1
  7. package/lib/components/forms/timePicker/helpers.d.ts +2 -5
  8. package/lib/index.css +0 -90
  9. package/lib/index.d.ts +3 -1
  10. package/lib/index.esm.css +0 -90
  11. package/lib/index.esm.js +187 -166
  12. package/lib/index.esm.js.map +1 -1
  13. package/lib/index.js +187 -166
  14. package/lib/index.js.map +1 -1
  15. package/package.json +1 -1
  16. package/src/components/forms/date/datePicker/DatePicker.stories.tsx +4 -4
  17. package/src/components/forms/date/datePicker/DatePicker.tsx +0 -2
  18. package/src/components/forms/date/inputDateTimePicker/SingleInputDateTimePicker.mdx +20 -0
  19. package/src/components/forms/date/inputDateTimePicker/SingleInputDateTimePicker.stories.tsx +262 -0
  20. package/src/components/forms/date/inputDateTimePicker/SingleInputDateTimePicker.tsx +141 -0
  21. package/src/components/forms/date/inputDateTimePicker/helpers.ts +3 -0
  22. package/src/components/forms/date/inputDateTimePicker/index.ts +1 -0
  23. package/src/components/forms/input/Input.tsx +1 -1
  24. package/src/components/forms/passwordInput/PasswordInput.stories.tsx +1 -1
  25. package/src/components/forms/subcomponents/DisplayFormError.tsx +7 -2
  26. package/src/components/forms/timePicker/TimePicker.mdx +3 -27
  27. package/src/components/forms/timePicker/TimePicker.stories.tsx +19 -1
  28. package/src/components/forms/timePicker/TimePicker.tsx +37 -80
  29. package/src/components/forms/timePicker/__tests__/TimePicker.test.tsx +33 -11
  30. package/src/components/forms/timePicker/helpers.ts +61 -13
  31. package/src/styles/index.scss +0 -2
  32. package/lib/components/forms/timePicker/constants.d.ts +0 -13
  33. package/src/components/forms/timePicker/constants.ts +0 -21
  34. package/src/components/forms/timePicker/styles/TimePicker.scss +0 -51
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indico-data/design-system",
3
- "version": "2.54.0",
3
+ "version": "2.55.0",
4
4
  "description": "",
5
5
  "author": "",
6
6
  "main": "lib/index.js",
@@ -284,7 +284,7 @@ export const Default: Story = {
284
284
  numberOfMonths: 1,
285
285
  isRequired: false,
286
286
  isDisabled: false,
287
- hasTimePicker: true,
287
+ hasTimePicker: false,
288
288
  timeValue: '00:00',
289
289
  },
290
290
  render: (args) => {
@@ -313,7 +313,7 @@ export const Single: Story = {
313
313
  isRequired: false,
314
314
  isDisabled: false,
315
315
  mode: 'single',
316
- hasTimePicker: true,
316
+ hasTimePicker: false,
317
317
  },
318
318
  render: (args) => {
319
319
  const [{ selected }, updateArgs] = useArgs();
@@ -329,7 +329,7 @@ export const Range: Story = {
329
329
  isRequired: false,
330
330
  isDisabled: false,
331
331
  mode: 'range',
332
- hasTimePicker: true,
332
+ hasTimePicker: false,
333
333
  },
334
334
  render: (args) => {
335
335
  const [{ selected, timeValue }, updateArgs] = useArgs();
@@ -358,7 +358,7 @@ export const Multi: Story = {
358
358
  isRequired: false,
359
359
  isDisabled: false,
360
360
  mode: 'multiple',
361
- hasTimePicker: true,
361
+ hasTimePicker: false,
362
362
  },
363
363
  render: (args) => {
364
364
  const [{ selected }, updateArgs] = useArgs();
@@ -1,7 +1,5 @@
1
- import React, { ChangeEventHandler, useState } from 'react';
2
1
  import { DateRange, DayPicker, Mode, OnSelectHandler } from 'react-day-picker';
3
2
  import 'react-day-picker/style.css';
4
-
5
3
  import { DatePickerProps } from './types';
6
4
  import { getCommonProps } from './contants';
7
5
  import { TimePicker } from '../../timePicker/TimePicker';
@@ -0,0 +1,20 @@
1
+ import { Canvas, Meta, Controls } from '@storybook/blocks';
2
+ import * as SingleInputDateTimePicker from './SingleInputDateTimePicker.stories';
3
+ import { Row, Col } from '../../../grid/index';
4
+
5
+ <Meta title="Forms/DatePicker/SingleInputDateTimePicker" name="SingleInputDateTimePicker" />
6
+
7
+ # Single Input Date Time Picker
8
+ The Single Input Date Time Picker is a visual representation of an input field that allows you to enter a date and time value to the input or select a date and time from the date picker. It leverages our DatePicker and TimePicker components inside of a FloatingUI window.
9
+
10
+ <Canvas of={SingleInputDateTimePicker.SingleInput} />
11
+
12
+ ## Props
13
+ In addition to the props listed below, it also accepts all props listed [here](story=?path=/docs/forms-datepicker--datepicker)
14
+ <Controls of={SingleInputDateTimePicker.SingleInput} />
15
+
16
+ ## Usage
17
+ This input is identical to the Single Input Date Picker with the exception that it also allows you to select a time value. There are only two values you need to provide, the `timeValue` and the `onTimeChange` callback function. The time input field is also readonly.
18
+
19
+ ## ToDo
20
+ - Create single component for all dropdown date pickers that accept an react element as the trigger, such as an icon, button, component, input, etc.
@@ -0,0 +1,262 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import {
3
+ SingleInputDateTimePicker,
4
+ SingleInputDateTimePickerProps,
5
+ } from './SingleInputDateTimePicker';
6
+ import { useArgs } from '@storybook/preview-api';
7
+ import { indiconDefinitions } from '@/components/icons/indicons';
8
+ import { registerFontAwesomeIcons } from '@/setup/setupIcons';
9
+
10
+ registerFontAwesomeIcons(...indiconDefinitions);
11
+
12
+ const meta: Meta<typeof SingleInputDateTimePicker> = {
13
+ component: SingleInputDateTimePicker,
14
+ title: 'Forms/DatePicker/SingleInputDateTimePicker',
15
+ decorators: [
16
+ (Story) => (
17
+ <div style={{ height: '400px' }}>
18
+ <Story />
19
+ </div>
20
+ ),
21
+ ],
22
+ argTypes: {
23
+ timeValue: {
24
+ control: 'text',
25
+ description: 'The time value to display.',
26
+ table: {
27
+ category: 'Props',
28
+ },
29
+ },
30
+ onTimeChange: {
31
+ action: 'timeChanged',
32
+ description: 'Callback function that is called when the time is changed.',
33
+ table: {
34
+ category: 'Events',
35
+ },
36
+ },
37
+ ariaLabel: {
38
+ control: 'text',
39
+ description: 'A label for assistive technologies.',
40
+ table: {
41
+ category: 'Props',
42
+ type: {
43
+ summary: 'string',
44
+ },
45
+ },
46
+ },
47
+ captionLayout: {
48
+ control: 'select',
49
+ options: ['dropdown', 'dropdown-months', 'dropdown-years', 'label'],
50
+ description:
51
+ 'The layout of the caption. Enables you to add or remove the dropdown navigation for months/years',
52
+ table: {
53
+ category: 'Props',
54
+ type: {
55
+ summary: 'dropdown | dropdown-months | dropdown-years | label',
56
+ },
57
+ },
58
+ },
59
+ className: {
60
+ control: false,
61
+ description: 'Accepts a CSS class name',
62
+ table: {
63
+ category: 'Props',
64
+ type: {
65
+ summary: 'string',
66
+ },
67
+ },
68
+ },
69
+ id: {
70
+ control: 'text',
71
+ description: 'The id of the input field.',
72
+ table: {
73
+ category: 'Props',
74
+ type: {
75
+ summary: 'string',
76
+ },
77
+ },
78
+ },
79
+ label: {
80
+ control: 'text',
81
+ description: 'The label for the input field.',
82
+ table: {
83
+ category: 'Props',
84
+ type: {
85
+ summary: 'string',
86
+ },
87
+ },
88
+ },
89
+ onSelect: {
90
+ action: 'selected',
91
+ description: 'Callback function that is called when a date is selected.',
92
+ table: {
93
+ category: 'Events',
94
+ type: {
95
+ summary: 'function',
96
+ },
97
+ },
98
+ },
99
+ selected: {
100
+ control: 'date',
101
+ description: 'The selected date.',
102
+ table: {
103
+ category: 'Props',
104
+ type: {
105
+ summary: 'Date',
106
+ },
107
+ },
108
+ },
109
+ inputPlaceholder: {
110
+ control: 'text',
111
+ description: 'The placeholder text for the input field.',
112
+ table: {
113
+ category: 'Props',
114
+ type: {
115
+ summary: 'string',
116
+ },
117
+ },
118
+ },
119
+ inputIconName: {
120
+ control: 'text',
121
+ description: 'The icon to display in the input field.',
122
+ table: {
123
+ category: 'Props',
124
+ type: {
125
+ summary: 'string',
126
+ },
127
+ },
128
+ },
129
+ isClearable: {
130
+ control: 'boolean',
131
+ description: 'Whether the input field should be clearable.',
132
+ table: {
133
+ category: 'Props',
134
+ type: {
135
+ summary: 'boolean',
136
+ },
137
+ },
138
+ },
139
+ isOpen: {
140
+ control: false,
141
+ description: 'Whether the date picker is open.',
142
+ table: {
143
+ category: 'Props',
144
+ type: {
145
+ summary: 'boolean',
146
+ },
147
+ },
148
+ },
149
+ clearOnClose: {
150
+ control: 'boolean',
151
+ description: 'Whether the input field should be cleared when the date picker is closed.',
152
+ table: {
153
+ category: 'Props',
154
+ type: {
155
+ summary: 'boolean',
156
+ },
157
+ },
158
+ },
159
+ errorMessage: {
160
+ control: 'text',
161
+ description: 'An error message to display.',
162
+ table: {
163
+ category: 'Props',
164
+ type: {
165
+ summary: 'string',
166
+ },
167
+ },
168
+ },
169
+ disableAfterDate: {
170
+ control: 'date',
171
+ description: 'Disable dates after this date.',
172
+ table: {
173
+ category: 'Props',
174
+ type: {
175
+ summary: 'Date',
176
+ },
177
+ },
178
+ },
179
+ disableBeforeDate: {
180
+ control: 'date',
181
+ description: 'Disable dates before this date.',
182
+ table: {
183
+ category: 'Props',
184
+ type: {
185
+ summary: 'Date',
186
+ },
187
+ },
188
+ },
189
+ isDisabled: {
190
+ control: 'boolean',
191
+ description: 'Disable the date picker.',
192
+ table: {
193
+ category: 'Props',
194
+ type: {
195
+ summary: 'boolean',
196
+ },
197
+ },
198
+ },
199
+ initialMonth: {
200
+ control: 'date',
201
+ description: 'The month to display.',
202
+ table: {
203
+ category: 'Props',
204
+ type: {
205
+ summary: 'Date',
206
+ },
207
+ },
208
+ },
209
+ 'data-testid': {
210
+ table: {
211
+ disable: true,
212
+ },
213
+ },
214
+ },
215
+ };
216
+
217
+ export default meta;
218
+
219
+ type Story = StoryObj<typeof SingleInputDateTimePicker>;
220
+
221
+ export const SingleInput: Story = {
222
+ args: {
223
+ id: 'date-picker',
224
+ label: 'Pick a date:',
225
+ inputPlaceholder: 'MM/DD/YYYY',
226
+ inputIconName: 'calendar',
227
+ isClearable: true,
228
+ isDisabled: false,
229
+ clearOnClose: false,
230
+ timeValue: '12:00 PM',
231
+ onTimeChange: () => {},
232
+ errorMessage: '',
233
+ ariaLabel: 'Date Picker',
234
+ selected: new Date(),
235
+ },
236
+ render: (args) => {
237
+ const [{ selected, timeValue }, updateArgs] = useArgs<SingleInputDateTimePickerProps>();
238
+
239
+ const handleSelect = (date: Date | undefined) => {
240
+ updateArgs({ selected: date });
241
+ };
242
+
243
+ const handleTimeChange = (time: string) => {
244
+ updateArgs({ timeValue: time });
245
+ };
246
+
247
+ return (
248
+ <>
249
+ <h1>Single Input Date Time Picker</h1>
250
+ <p>Date: {selected?.toLocaleDateString()}</p>
251
+ <p>Time: {timeValue}</p>
252
+ <SingleInputDateTimePicker
253
+ {...args}
254
+ selected={selected}
255
+ timeValue={timeValue}
256
+ onSelect={handleSelect}
257
+ onTimeChange={handleTimeChange}
258
+ />
259
+ </>
260
+ );
261
+ },
262
+ };
@@ -0,0 +1,141 @@
1
+ import { useId, useState, useEffect } from 'react';
2
+ import { isValid, parse } from 'date-fns';
3
+ import { DatePicker } from '../datePicker/DatePicker';
4
+ import { Input } from '../../input';
5
+ import type { IconName } from '../../../icons/types';
6
+ import { FloatUI } from '../../../floatUI';
7
+ import { formatDateAsString } from './helpers';
8
+ import { TimePicker } from '../../timePicker/TimePicker';
9
+ import { Row, Col } from '../../../grid';
10
+
11
+ export interface SingleInputDateTimePickerProps {
12
+ ariaLabel: string;
13
+ disableBeforeDate?: Date;
14
+ disableAfterDate?: Date;
15
+ isDisabled?: boolean;
16
+ captionLayout?: 'dropdown' | 'dropdown-months' | 'dropdown-years' | 'label';
17
+ id?: string;
18
+ label?: string;
19
+ onSelect: (selected: Date | undefined) => void;
20
+ initialMonth?: Date;
21
+ selected?: Date;
22
+ isOpen?: boolean;
23
+ clearOnClose?: boolean;
24
+ className?: string;
25
+ inputIconName?: IconName;
26
+ isClearable?: boolean;
27
+ inputPlaceholder?: string;
28
+ errorMessage?: string | undefined;
29
+ 'data-testid'?: string;
30
+ timeValue?: string;
31
+ onTimeChange?: (time: string) => void;
32
+ }
33
+
34
+ export function SingleInputDateTimePicker(props: SingleInputDateTimePickerProps) {
35
+ const {
36
+ ariaLabel,
37
+ className,
38
+ isDisabled,
39
+ disableBeforeDate,
40
+ disableAfterDate,
41
+ captionLayout,
42
+ initialMonth,
43
+ id,
44
+ label,
45
+ selected,
46
+ isOpen,
47
+ inputPlaceholder,
48
+ clearOnClose,
49
+ inputIconName,
50
+ isClearable,
51
+ errorMessage,
52
+ onSelect,
53
+ timeValue,
54
+ onTimeChange,
55
+ ...rest
56
+ } = props;
57
+
58
+ const inputId = useId();
59
+
60
+ // The text value is assumed to be unneeded by the consumer.
61
+ const [localTextValue, setLocalTextValue] = useState<string>(
62
+ selected ? formatDateAsString(selected) : '',
63
+ );
64
+
65
+ const [localMonth, setLocalMonth] = useState<Date>(initialMonth ?? selected ?? new Date());
66
+
67
+ // When the day picker is selected, update the text value.
68
+ const handleDayPickerSelect = (date: Date | undefined) => {
69
+ if (!date) {
70
+ setLocalTextValue('');
71
+ onSelect(undefined);
72
+ } else {
73
+ setLocalTextValue(formatDateAsString(date));
74
+ onSelect(date);
75
+ }
76
+ };
77
+
78
+ // When the text input is changed, update the selected date.
79
+ const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
80
+ setLocalTextValue(e.target.value); // keep the input value in sync
81
+
82
+ const parsedDate = parse(e.target.value, 'MM/dd/yyyy', new Date());
83
+
84
+ if (isValid(parsedDate)) {
85
+ onSelect(parsedDate);
86
+ } else {
87
+ onSelect(undefined);
88
+ }
89
+ };
90
+
91
+ // clear selection if clear on close is true
92
+ useEffect(() => {
93
+ if (!isOpen && clearOnClose) {
94
+ onSelect(undefined);
95
+ setLocalTextValue('');
96
+ }
97
+ }, [isOpen, clearOnClose]);
98
+
99
+ const handleTimeChange = (time: string) => {
100
+ onTimeChange?.(time);
101
+ };
102
+
103
+ return (
104
+ <Row>
105
+ <Col>
106
+ <FloatUI isOpen={isOpen} ariaLabel={ariaLabel}>
107
+ <Input
108
+ id={inputId}
109
+ value={localTextValue}
110
+ placeholder={inputPlaceholder}
111
+ isDisabled={isDisabled}
112
+ iconName={inputIconName}
113
+ isClearable={isClearable}
114
+ onChange={handleInputChange}
115
+ errorMessage={errorMessage}
116
+ hasHiddenLabel
117
+ label={'Single Date Picker'}
118
+ name={`${id}-date-picker`}
119
+ />
120
+ <DatePicker
121
+ captionLayout={captionLayout}
122
+ mode="single"
123
+ selected={selected}
124
+ onSelect={handleDayPickerSelect}
125
+ month={localMonth}
126
+ onMonthChange={setLocalMonth}
127
+ {...rest}
128
+ />
129
+ </FloatUI>
130
+ </Col>
131
+ <Col xs="content">
132
+ <TimePicker
133
+ timeValue={timeValue}
134
+ name={`${id}-time-picker`}
135
+ hasHiddenLabel
136
+ onTimeChange={handleTimeChange}
137
+ />
138
+ </Col>
139
+ </Row>
140
+ );
141
+ }
@@ -0,0 +1,3 @@
1
+ import { format } from 'date-fns';
2
+
3
+ export const formatDateAsString = (date: Date) => format(date, 'MM/dd/yyyy');
@@ -0,0 +1 @@
1
+ export { SingleInputDateTimePicker } from './SingleInputDateTimePicker';
@@ -97,7 +97,7 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
97
97
  />
98
98
  )}
99
99
  </div>
100
- {hasErrors && <DisplayFormError message={errorMessage} />}
100
+ {hasErrors && <DisplayFormError data-testid={`${name}-error`} message={errorMessage} />}
101
101
  {helpText && (
102
102
  <div data-testid={`${name}-help-text`} className="help-text" id={`${name}-helper`}>
103
103
  {helpText}
@@ -1,7 +1,7 @@
1
1
  import { Meta, StoryObj } from '@storybook/react';
2
2
  import { PasswordInput, PasswordInputProps } from './PasswordInput';
3
3
  import { SetStateAction, useEffect, useState } from 'react';
4
- import { baseInputArgTypes, inputArgTypes, labelArgTypes } from '@/storybook/formArgTypes';
4
+ import { baseInputArgTypes, labelArgTypes } from '@/storybook/formArgTypes';
5
5
 
6
6
  const meta: Meta = {
7
7
  title: 'Forms/PasswordInput',
@@ -1,7 +1,12 @@
1
1
  interface DisplayFormErrorProps {
2
2
  message: string | undefined;
3
+ [key: string]: any;
3
4
  }
4
5
 
5
- export const DisplayFormError = ({ message }: DisplayFormErrorProps) => {
6
- return <p className="form-errors">{message}</p>;
6
+ export const DisplayFormError = ({ message, ...rest }: DisplayFormErrorProps) => {
7
+ return (
8
+ <p className="form-errors" {...rest}>
9
+ {message}
10
+ </p>
11
+ );
7
12
  };
@@ -5,33 +5,9 @@ import { Row, Col } from '../../grid/index';
5
5
  <Meta title="Forms/TimePicker" name="TimePicker" />
6
6
 
7
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
- />
8
+ The time picker component is a simple time input field that validates the user's input and formats it if the value is valid. If the value is invalid, it will display an error message and not fire the callback.
9
+
10
+ <Canvas of={TimePicker.Default} />
35
11
 
36
12
  <Controls of={TimePicker.Default} />
37
13
 
@@ -7,13 +7,29 @@ const meta: Meta<typeof TimePicker> = {
7
7
  title: 'Forms/TimePicker',
8
8
  component: TimePicker,
9
9
  argTypes: {
10
+ hasHiddenLabel: {
11
+ control: 'boolean',
12
+ description: 'Hides the input label in an accessible way (visually hides the label)',
13
+ defaultValue: false,
14
+ table: {
15
+ category: 'Props',
16
+ },
17
+ },
18
+ name: {
19
+ control: 'text',
20
+ description: 'The name of the input field',
21
+ defaultValue: 'time-picker',
22
+ table: {
23
+ category: 'Props',
24
+ },
25
+ },
10
26
  onTimeChange: {
11
27
  control: false,
12
28
  description: 'returns an object with the time and errors if they exist',
13
29
  table: {
14
30
  category: 'Callbacks',
15
31
  type: {
16
- summary: '{ time: string, period: string, errors: Record<string, string> }',
32
+ summary: '{ time: string } => void',
17
33
  },
18
34
  },
19
35
  },
@@ -55,6 +71,8 @@ export const Default: Story = {
55
71
  args: {
56
72
  timeValue: '12:00 AM',
57
73
  label: 'Time Picker',
74
+ name: 'time-picker',
75
+ hasHiddenLabel: true,
58
76
  },
59
77
 
60
78
  render: (args) => {