@indico-data/design-system 2.52.0 → 2.54.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 (90) hide show
  1. package/lib/components/forms/date/datePicker/types.d.ts +3 -0
  2. package/lib/components/forms/input/Input.d.ts +2 -1
  3. package/lib/components/forms/timePicker/TimePicker.d.ts +7 -0
  4. package/lib/components/forms/timePicker/TimePicker.stories.d.ts +6 -0
  5. package/lib/components/forms/timePicker/__tests__/TimePicker.test.d.ts +1 -0
  6. package/lib/components/forms/timePicker/constants.d.ts +13 -0
  7. package/lib/components/forms/timePicker/helpers.d.ts +5 -0
  8. package/lib/components/forms/timePicker/index.d.ts +1 -0
  9. package/lib/components/index.d.ts +3 -0
  10. package/lib/components/loading-indicators/BarSpinner/BarSpinner.d.ts +8 -0
  11. package/lib/components/loading-indicators/BarSpinner/BarSpinner.stories.d.ts +7 -0
  12. package/lib/components/loading-indicators/BarSpinner/__tests__/BarSpinner.test.d.ts +1 -0
  13. package/lib/components/loading-indicators/CirclePulse/CirclePulse.d.ts +7 -0
  14. package/lib/components/loading-indicators/CirclePulse/CirclePulse.stories.d.ts +6 -0
  15. package/lib/components/tooltip/Tooltip.d.ts +13 -0
  16. package/lib/components/tooltip/Tooltip.stories.d.ts +6 -0
  17. package/lib/index.css +269 -1
  18. package/lib/index.d.ts +62 -47
  19. package/lib/index.esm.css +269 -1
  20. package/lib/index.esm.js +6080 -6235
  21. package/lib/index.esm.js.map +1 -1
  22. package/lib/index.js +6079 -6233
  23. package/lib/index.js.map +1 -1
  24. package/package.json +1 -1
  25. package/src/components/forms/date/datePicker/DatePicker.stories.tsx +67 -4
  26. package/src/components/forms/date/datePicker/DatePicker.tsx +28 -2
  27. package/src/components/forms/date/datePicker/styles/DatePicker.scss +9 -0
  28. package/src/components/forms/date/datePicker/types.ts +3 -0
  29. package/src/components/forms/form/styles/Form.scss +1 -0
  30. package/src/components/forms/input/Input.tsx +5 -2
  31. package/src/components/forms/numberInput/NumberInput.tsx +1 -1
  32. package/src/components/forms/timePicker/TimePicker.mdx +39 -0
  33. package/src/components/forms/timePicker/TimePicker.stories.tsx +79 -0
  34. package/src/components/forms/timePicker/TimePicker.tsx +104 -0
  35. package/src/components/forms/timePicker/__tests__/TimePicker.test.tsx +26 -0
  36. package/src/components/forms/timePicker/constants.ts +21 -0
  37. package/src/components/forms/timePicker/helpers.ts +14 -0
  38. package/src/components/forms/timePicker/index.ts +1 -0
  39. package/src/components/forms/timePicker/styles/TimePicker.scss +51 -0
  40. package/src/components/index.ts +3 -0
  41. package/src/components/loading-indicators/BarSpinner/BarSpinner.mdx +21 -0
  42. package/src/components/loading-indicators/BarSpinner/BarSpinner.stories.tsx +89 -0
  43. package/src/components/loading-indicators/BarSpinner/BarSpinner.tsx +25 -0
  44. package/src/components/loading-indicators/BarSpinner/__tests__/BarSpinner.test.tsx +16 -0
  45. package/src/{legacy/components/loading-indicators/BarSpinner/BarSpinner.styles.ts → components/loading-indicators/BarSpinner/styles/BarSpinner.scss} +11 -11
  46. package/src/components/loading-indicators/CirclePulse/CirclePulse.mdx +20 -0
  47. package/src/components/loading-indicators/CirclePulse/CirclePulse.scss +83 -0
  48. package/src/components/loading-indicators/CirclePulse/CirclePulse.stories.tsx +78 -0
  49. package/src/components/loading-indicators/CirclePulse/CirclePulse.tsx +15 -0
  50. package/src/components/tanstackTable/components/TableBody/TableBody.tsx +1 -1
  51. package/src/components/tooltip/Tooltip.mdx +25 -0
  52. package/src/components/tooltip/Tooltip.stories.tsx +113 -0
  53. package/src/components/tooltip/Tooltip.tsx +38 -0
  54. package/src/components/tooltip/styles/Tooltip.scss +8 -0
  55. package/src/index.ts +5 -2
  56. package/src/styles/globals.scss +9 -0
  57. package/src/styles/index.scss +5 -1
  58. package/lib/legacy/components/Tooltip/Tooltip.d.ts +0 -15
  59. package/lib/legacy/components/Tooltip/Tooltip.stories.d.ts +0 -9
  60. package/lib/legacy/components/Tooltip/Tooltip.styles.d.ts +0 -1
  61. package/lib/legacy/components/index.d.ts +0 -2
  62. package/lib/legacy/components/loading-indicators/BarSpinner/BarSpinner.d.ts +0 -7
  63. package/lib/legacy/components/loading-indicators/BarSpinner/BarSpinner.stories.d.ts +0 -10
  64. package/lib/legacy/components/loading-indicators/BarSpinner/BarSpinner.styles.d.ts +0 -1
  65. package/lib/legacy/components/loading-indicators/CirclePulse/CirclePulse.d.ts +0 -10
  66. package/lib/legacy/components/loading-indicators/CirclePulse/CirclePulse.stories.d.ts +0 -12
  67. package/lib/legacy/components/loading-indicators/CirclePulse/CirclePulse.styles.d.ts +0 -7
  68. package/lib/legacy/components/loading-indicators/CircleSpinner/CircleSpinner.d.ts +0 -10
  69. package/lib/legacy/components/loading-indicators/CircleSpinner/CircleSpinner.stories.d.ts +0 -10
  70. package/lib/legacy/components/loading-indicators/CircleSpinner/index.d.ts +0 -1
  71. package/lib/legacy/components/loading-indicators/index.d.ts +0 -3
  72. package/src/legacy/components/Tooltip/Tooltip.stories.tsx +0 -107
  73. package/src/legacy/components/Tooltip/Tooltip.styles.ts +0 -64
  74. package/src/legacy/components/Tooltip/Tooltip.tsx +0 -70
  75. package/src/legacy/components/index.ts +0 -2
  76. package/src/legacy/components/loading-indicators/BarSpinner/BarSpinner.stories.tsx +0 -14
  77. package/src/legacy/components/loading-indicators/BarSpinner/BarSpinner.tsx +0 -21
  78. package/src/legacy/components/loading-indicators/CirclePulse/CirclePulse.stories.tsx +0 -22
  79. package/src/legacy/components/loading-indicators/CirclePulse/CirclePulse.styles.ts +0 -77
  80. package/src/legacy/components/loading-indicators/CirclePulse/CirclePulse.tsx +0 -57
  81. package/src/legacy/components/loading-indicators/CircleSpinner/CircleSpinner.stories.tsx +0 -16
  82. package/src/legacy/components/loading-indicators/CircleSpinner/CircleSpinner.tsx +0 -36
  83. package/src/legacy/components/loading-indicators/CircleSpinner/index.ts +0 -1
  84. package/src/legacy/components/loading-indicators/index.ts +0 -3
  85. /package/lib/{legacy/components → components}/loading-indicators/BarSpinner/index.d.ts +0 -0
  86. /package/lib/{legacy/components → components}/loading-indicators/CirclePulse/index.d.ts +0 -0
  87. /package/lib/{legacy/components/Tooltip → components/tooltip}/index.d.ts +0 -0
  88. /package/src/{legacy/components → components}/loading-indicators/BarSpinner/index.ts +0 -0
  89. /package/src/{legacy/components → components}/loading-indicators/CirclePulse/index.ts +0 -0
  90. /package/src/{legacy/components/Tooltip → components/tooltip}/index.ts +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indico-data/design-system",
3
- "version": "2.52.0",
3
+ "version": "2.54.0",
4
4
  "description": "",
5
5
  "author": "",
6
6
  "main": "lib/index.js",
@@ -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
- return <DatePicker {...args} selected={selected} onSelect={handleSelect} />;
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
- return <DatePicker {...args} selected={selected} onSelect={handleSelect} />;
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
- return <DayPicker {...finalProps} />;
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
  };
@@ -85,3 +85,12 @@
85
85
  }
86
86
  }
87
87
  }
88
+
89
+ .time-picker-wrapper {
90
+ margin-bottom: var(--pf-margin-3);
91
+ width: 348px;
92
+
93
+ .form-control {
94
+ margin-bottom: 0;
95
+ }
96
+ }
@@ -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;
@@ -37,6 +37,7 @@ form {
37
37
  }
38
38
 
39
39
  // Common input/textarea form element styles
40
+ .time-input-wrapper,
40
41
  .input-wrapper,
41
42
  .number-input-wrapper,
42
43
  .password-input-wrapper,
@@ -12,10 +12,11 @@ export interface BaseInputProps {
12
12
  placeholder?: string;
13
13
  isDisabled?: boolean;
14
14
  readonly?: boolean;
15
- onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
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
+ }
@@ -22,3 +22,6 @@ export { Modal } from './modal';
22
22
  export { Badge } from './badge';
23
23
  export { Pagination } from './pagination';
24
24
  export { TanstackTable } from './tanstackTable';
25
+ export { Tooltip } from './tooltip';
26
+ export { BarSpinner } from './loading-indicators/BarSpinner/BarSpinner';
27
+ export { CirclePulse } from './loading-indicators/CirclePulse/CirclePulse';
@@ -0,0 +1,21 @@
1
+ import { Canvas, Meta, Controls, Story } from '@storybook/blocks';
2
+ import * as BarSpinner from './BarSpinner.stories';
3
+
4
+ <Meta title="Utilities/BarSpinner" name="BarSpinner" of={BarSpinner} />
5
+
6
+ # BarSpinner
7
+
8
+ This is a simple looping animation component that can indicate a loading state
9
+
10
+ <Canvas of={BarSpinner.Default} />
11
+
12
+ ### The following props are available for the BarSpinner component:
13
+
14
+ <Controls of={BarSpinner.Default} />
15
+
16
+ ## Usage
17
+ Simple usage of the BarSpinner component:
18
+ <Canvas of={BarSpinner.WithWidthAndHeight} />
19
+ ```tsx
20
+ <BarSpinner id="bar-spinner-optional-id" className="optional-class-name" width={200} height={10} />
21
+ ```