playbook_ui 15.6.0.pre.rc.4 → 15.7.0.pre.alpha.PLAY2678emojimask13284

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 (202) hide show
  1. checksums.yaml +4 -4
  2. data/app/pb_kits/playbook/_playbook.scss +1 -1
  3. data/app/pb_kits/playbook/pb_advanced_table/Components/RegularTableView.tsx +3 -2
  4. data/app/pb_kits/playbook/pb_advanced_table/Components/TableHeaderCell.tsx +4 -0
  5. data/app/pb_kits/playbook/pb_advanced_table/advanced_table.test.jsx +95 -0
  6. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_background_colors_rails.html.erb +43 -0
  7. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_background_colors_rails.md +1 -0
  8. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_background_control_rails.html.erb +11 -5
  9. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_background_control_rails.md +7 -1
  10. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_column_styling_background.jsx +54 -0
  11. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_column_styling_background.md +9 -0
  12. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_column_styling_background_multi.jsx +80 -0
  13. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_column_styling_background_multi.md +3 -0
  14. data/app/pb_kits/playbook/pb_advanced_table/docs/example.yml +4 -1
  15. data/app/pb_kits/playbook/pb_advanced_table/docs/index.js +3 -1
  16. data/app/pb_kits/playbook/pb_advanced_table/table_header.html.erb +2 -2
  17. data/app/pb_kits/playbook/pb_advanced_table/table_header.rb +57 -0
  18. data/app/pb_kits/playbook/pb_bar_graph/_bar_graph.tsx +6 -0
  19. data/app/pb_kits/playbook/pb_card/docs/_card_header.md +1 -1
  20. data/app/pb_kits/playbook/pb_card/docs/_card_highlight.md +1 -1
  21. data/app/pb_kits/playbook/pb_circle_chart/_circle_chart.tsx +6 -0
  22. data/app/pb_kits/playbook/pb_collapsible/__snapshots__/collapsible.test.js.snap +2 -2
  23. data/app/pb_kits/playbook/pb_collapsible/child_kits/CollapsibleIcon.tsx +10 -8
  24. data/app/pb_kits/playbook/pb_collapsible/docs/_collapsible_icons.jsx +0 -1
  25. data/app/pb_kits/playbook/pb_collapsible/docs/_collapsible_state.jsx +0 -3
  26. data/app/pb_kits/playbook/pb_contact/_contact.tsx +51 -24
  27. data/app/pb_kits/playbook/pb_contact/contact.html.erb +53 -19
  28. data/app/pb_kits/playbook/pb_contact/contact.rb +11 -1
  29. data/app/pb_kits/playbook/pb_contact/contact.test.js +76 -0
  30. data/app/pb_kits/playbook/pb_contact/docs/_contact_unstyled.html.erb +33 -0
  31. data/app/pb_kits/playbook/pb_contact/docs/_contact_unstyled.jsx +46 -0
  32. data/app/pb_kits/playbook/pb_contact/docs/_contact_unstyled_rails.md +2 -0
  33. data/app/pb_kits/playbook/pb_contact/docs/_contact_unstyled_react.md +2 -0
  34. data/app/pb_kits/playbook/pb_contact/docs/example.yml +2 -0
  35. data/app/pb_kits/playbook/pb_contact/docs/index.js +1 -0
  36. data/app/pb_kits/playbook/pb_date_picker/date_picker.test.js +24 -0
  37. data/app/pb_kits/playbook/pb_date_picker/date_picker_helper.ts +197 -7
  38. data/app/pb_kits/playbook/pb_date_picker/docs/_date_picker_range_pattern_rails.html.erb +23 -14
  39. data/app/pb_kits/playbook/pb_date_picker/docs/_date_picker_range_pattern_rails.md +1 -1
  40. data/app/pb_kits/playbook/pb_dialog/_dialog.tsx +2 -1
  41. data/app/pb_kits/playbook/pb_dialog/dialog.html.erb +1 -1
  42. data/app/pb_kits/playbook/pb_dialog/dialog.rb +1 -0
  43. data/app/pb_kits/playbook/pb_dialog/dialog.test.jsx +14 -0
  44. data/app/pb_kits/playbook/pb_dialog/dialog_header.html.erb +5 -4
  45. data/app/pb_kits/playbook/pb_dialog/dialog_header.rb +2 -0
  46. data/app/pb_kits/playbook/pb_dialog/docs/_dialog_closeable.html.erb +24 -0
  47. data/app/pb_kits/playbook/pb_dialog/docs/_dialog_closeable.jsx +60 -0
  48. data/app/pb_kits/playbook/pb_dialog/docs/_dialog_closeable.md +3 -0
  49. data/app/pb_kits/playbook/pb_dialog/docs/_dialog_overflow_visible.html.erb +71 -0
  50. data/app/pb_kits/playbook/pb_dialog/docs/_dialog_overflow_visible.jsx +57 -0
  51. data/app/pb_kits/playbook/pb_dialog/docs/_dialog_overflow_visible_rails.md +1 -0
  52. data/app/pb_kits/playbook/pb_dialog/docs/_dialog_overflow_visible_react.md +1 -0
  53. data/app/pb_kits/playbook/pb_dialog/docs/example.yml +4 -0
  54. data/app/pb_kits/playbook/pb_dialog/docs/index.js +3 -1
  55. data/app/pb_kits/playbook/pb_distribution_bar/docs/_distribution_bar_custom_colors.md +1 -1
  56. data/app/pb_kits/playbook/pb_draggable/context/index.tsx +316 -15
  57. data/app/pb_kits/playbook/pb_draggable/context/types.ts +1 -1
  58. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_default_rails.html.erb +7 -5
  59. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_quickpick_default_dates.html.erb +19 -0
  60. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_quickpick_rails.html.erb +12 -0
  61. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_quickpick_rails.md +26 -0
  62. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_quickpick_range_end_rails.html.erb +19 -0
  63. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_quickpick_range_end_rails.md +1 -0
  64. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_quickpick_with_date_pickers_default_rails.html.erb +30 -0
  65. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_quickpick_with_date_pickers_default_rails.md +3 -0
  66. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_quickpick_with_date_pickers_rails.html.erb +29 -0
  67. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_quickpick_with_date_pickers_rails.md +13 -0
  68. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_custom_display_rails.html.erb +3 -1
  69. data/app/pb_kits/playbook/pb_dropdown/docs/example.yml +5 -0
  70. data/app/pb_kits/playbook/pb_dropdown/dropdown.html.erb +4 -0
  71. data/app/pb_kits/playbook/pb_dropdown/dropdown.rb +39 -5
  72. data/app/pb_kits/playbook/pb_dropdown/index.js +171 -3
  73. data/app/pb_kits/playbook/pb_dropdown/quickpick_helper.rb +75 -0
  74. data/app/pb_kits/playbook/pb_filter/Filter/FilterBackground.tsx +3 -3
  75. data/app/pb_kits/playbook/pb_form/docs/_form_form_with.html.erb +3 -3
  76. data/app/pb_kits/playbook/pb_form/docs/_form_form_with_validate.html.erb +2 -1
  77. data/app/pb_kits/playbook/pb_form/docs/_form_with_required_indicator.html.erb +14 -0
  78. data/app/pb_kits/playbook/pb_form/docs/_form_with_required_indicator.md +3 -0
  79. data/app/pb_kits/playbook/pb_form/docs/example.yml +1 -0
  80. data/app/pb_kits/playbook/pb_gauge/_gauge.tsx +6 -0
  81. data/app/pb_kits/playbook/pb_line_graph/_line_graph.tsx +6 -0
  82. data/app/pb_kits/playbook/pb_popover/docs/_popover_append_to.html.erb +2 -2
  83. data/app/pb_kits/playbook/pb_popover/docs/_popover_append_to.jsx +3 -2
  84. data/app/pb_kits/playbook/pb_radio/docs/_radio_error.md +1 -1
  85. data/app/pb_kits/playbook/pb_select/_select.tsx +8 -3
  86. data/app/pb_kits/playbook/pb_select/docs/_select_error.md +1 -1
  87. data/app/pb_kits/playbook/pb_select/docs/_select_input_options.html.erb +16 -0
  88. data/app/pb_kits/playbook/pb_select/docs/_select_input_options.jsx +30 -0
  89. data/app/pb_kits/playbook/pb_select/docs/_select_input_options.md +1 -0
  90. data/app/pb_kits/playbook/pb_select/docs/example.yml +2 -0
  91. data/app/pb_kits/playbook/pb_select/docs/index.js +1 -0
  92. data/app/pb_kits/playbook/pb_select/select.html.erb +2 -2
  93. data/app/pb_kits/playbook/pb_select/select.rb +3 -1
  94. data/app/pb_kits/playbook/pb_select/select.test.js +23 -0
  95. data/app/pb_kits/playbook/pb_table/_table.tsx +187 -33
  96. data/app/pb_kits/playbook/pb_table/docs/_table_with_filter_variant.jsx +134 -0
  97. data/app/pb_kits/playbook/pb_table/docs/_table_with_filter_variant.md +34 -0
  98. data/app/pb_kits/playbook/pb_table/docs/_table_with_filter_variant_rails.html.erb +101 -0
  99. data/app/pb_kits/playbook/pb_table/docs/_table_with_filter_variant_rails.md +33 -0
  100. data/app/pb_kits/playbook/pb_table/docs/_table_with_filter_variant_with_pagination.jsx +180 -0
  101. data/app/pb_kits/playbook/pb_table/docs/_table_with_filter_variant_with_pagination.md +3 -0
  102. data/app/pb_kits/playbook/pb_table/docs/_table_with_filter_variant_with_pagination_rails.html.erb +122 -0
  103. data/app/pb_kits/playbook/pb_table/docs/_table_with_filter_variant_with_pagination_rails.md +3 -0
  104. data/app/pb_kits/playbook/pb_table/docs/example.yml +4 -0
  105. data/app/pb_kits/playbook/pb_table/docs/index.js +2 -0
  106. data/app/pb_kits/playbook/pb_table/table.html.erb +68 -12
  107. data/app/pb_kits/playbook/pb_table/table.rb +22 -3
  108. data/app/pb_kits/playbook/pb_table/table.test.js +143 -0
  109. data/app/pb_kits/playbook/pb_text_input/_text_input.tsx +56 -6
  110. data/app/pb_kits/playbook/pb_text_input/docs/_text_input_emoji_mask.html.erb +7 -0
  111. data/app/pb_kits/playbook/pb_text_input/docs/_text_input_emoji_mask.jsx +24 -0
  112. data/app/pb_kits/playbook/pb_text_input/docs/_text_input_emoji_mask.md +2 -0
  113. data/app/pb_kits/playbook/pb_text_input/docs/_text_input_error.md +1 -1
  114. data/app/pb_kits/playbook/pb_text_input/docs/_text_input_required_indicator.html.erb +6 -0
  115. data/app/pb_kits/playbook/pb_text_input/docs/_text_input_required_indicator.jsx +25 -0
  116. data/app/pb_kits/playbook/pb_text_input/docs/_text_input_required_indicator.md +3 -0
  117. data/app/pb_kits/playbook/pb_text_input/docs/example.yml +5 -0
  118. data/app/pb_kits/playbook/pb_text_input/docs/index.js +2 -0
  119. data/app/pb_kits/playbook/pb_text_input/index.js +49 -8
  120. data/app/pb_kits/playbook/pb_text_input/text_input.html.erb +6 -0
  121. data/app/pb_kits/playbook/pb_text_input/text_input.rb +7 -1
  122. data/app/pb_kits/playbook/pb_text_input/text_input.test.js +69 -0
  123. data/app/pb_kits/playbook/pb_textarea/_textarea.tsx +38 -2
  124. data/app/pb_kits/playbook/pb_textarea/docs/_textarea_emoji_mask.html.erb +5 -0
  125. data/app/pb_kits/playbook/pb_textarea/docs/_textarea_emoji_mask.jsx +24 -0
  126. data/app/pb_kits/playbook/pb_textarea/docs/_textarea_emoji_mask.md +1 -0
  127. data/app/pb_kits/playbook/pb_textarea/docs/_textarea_error.md +1 -1
  128. data/app/pb_kits/playbook/pb_textarea/docs/example.yml +2 -0
  129. data/app/pb_kits/playbook/pb_textarea/docs/index.js +1 -0
  130. data/app/pb_kits/playbook/pb_textarea/index.ts +62 -5
  131. data/app/pb_kits/playbook/pb_textarea/textarea.html.erb +1 -0
  132. data/app/pb_kits/playbook/pb_textarea/textarea.rb +8 -0
  133. data/app/pb_kits/playbook/pb_textarea/textarea.test.js +57 -2
  134. data/app/pb_kits/playbook/pb_time_picker/_time_picker.scss +296 -0
  135. data/app/pb_kits/playbook/pb_time_picker/_time_picker.tsx +822 -0
  136. data/app/pb_kits/playbook/pb_time_picker/docs/_time_picker_24_hour.html.erb +2 -0
  137. data/app/pb_kits/playbook/pb_time_picker/docs/_time_picker_24_hour.jsx +16 -0
  138. data/app/pb_kits/playbook/pb_time_picker/docs/_time_picker_24_hour.md +1 -0
  139. data/app/pb_kits/playbook/pb_time_picker/docs/_time_picker_default.html.erb +1 -0
  140. data/app/pb_kits/playbook/pb_time_picker/docs/_time_picker_default.jsx +13 -0
  141. data/app/pb_kits/playbook/pb_time_picker/docs/_time_picker_default.md +1 -0
  142. data/app/pb_kits/playbook/pb_time_picker/docs/_time_picker_default_time.html.erb +4 -0
  143. data/app/pb_kits/playbook/pb_time_picker/docs/_time_picker_default_time.jsx +29 -0
  144. data/app/pb_kits/playbook/pb_time_picker/docs/_time_picker_default_time.md +1 -0
  145. data/app/pb_kits/playbook/pb_time_picker/docs/_time_picker_disabled.html.erb +13 -0
  146. data/app/pb_kits/playbook/pb_time_picker/docs/_time_picker_disabled.jsx +23 -0
  147. data/app/pb_kits/playbook/pb_time_picker/docs/_time_picker_error.html.erb +5 -0
  148. data/app/pb_kits/playbook/pb_time_picker/docs/_time_picker_error.jsx +15 -0
  149. data/app/pb_kits/playbook/pb_time_picker/docs/_time_picker_input_options.html.erb +14 -0
  150. data/app/pb_kits/playbook/pb_time_picker/docs/_time_picker_label.html.erb +2 -0
  151. data/app/pb_kits/playbook/pb_time_picker/docs/_time_picker_label.jsx +15 -0
  152. data/app/pb_kits/playbook/pb_time_picker/docs/_time_picker_min_max_time.html.erb +42 -0
  153. data/app/pb_kits/playbook/pb_time_picker/docs/_time_picker_min_max_time.jsx +52 -0
  154. data/app/pb_kits/playbook/pb_time_picker/docs/_time_picker_min_max_time.md +1 -0
  155. data/app/pb_kits/playbook/pb_time_picker/docs/_time_picker_on_handler.jsx +45 -0
  156. data/app/pb_kits/playbook/pb_time_picker/docs/_time_picker_on_handler.md +1 -0
  157. data/app/pb_kits/playbook/pb_time_picker/docs/_time_picker_timezone.html.erb +3 -0
  158. data/app/pb_kits/playbook/pb_time_picker/docs/_time_picker_timezone.jsx +21 -0
  159. data/app/pb_kits/playbook/pb_time_picker/docs/_time_picker_timezone.md +1 -0
  160. data/app/pb_kits/playbook/pb_time_picker/docs/example.yml +24 -0
  161. data/app/pb_kits/playbook/pb_time_picker/docs/index.js +9 -0
  162. data/app/pb_kits/playbook/pb_time_picker/index.ts +40 -0
  163. data/app/pb_kits/playbook/pb_time_picker/time_picker.html.erb +1 -0
  164. data/app/pb_kits/playbook/pb_time_picker/time_picker.rb +80 -0
  165. data/app/pb_kits/playbook/pb_time_picker/time_picker.test.jsx +114 -0
  166. data/app/pb_kits/playbook/pb_time_picker/time_picker_helper.ts +662 -0
  167. data/app/pb_kits/playbook/pb_timeline/_item.tsx +3 -0
  168. data/app/pb_kits/playbook/pb_timeline/docs/_timeline_show_current_year.html.erb +60 -0
  169. data/app/pb_kits/playbook/pb_timeline/docs/_timeline_show_current_year.jsx +118 -0
  170. data/app/pb_kits/playbook/pb_timeline/docs/_timeline_show_current_year.md +1 -0
  171. data/app/pb_kits/playbook/pb_timeline/docs/_timeline_with_date.md +1 -1
  172. data/app/pb_kits/playbook/pb_timeline/docs/example.yml +2 -0
  173. data/app/pb_kits/playbook/pb_timeline/docs/index.js +1 -0
  174. data/app/pb_kits/playbook/pb_timeline/item.html.erb +1 -1
  175. data/app/pb_kits/playbook/pb_timeline/item.rb +2 -0
  176. data/app/pb_kits/playbook/pb_timeline/label.html.erb +2 -1
  177. data/app/pb_kits/playbook/pb_timeline/label.rb +2 -0
  178. data/app/pb_kits/playbook/pb_timeline/subcomponents/Label.tsx +3 -0
  179. data/app/pb_kits/playbook/pb_timeline/timeline.test.js +51 -0
  180. data/app/pb_kits/playbook/tokens/_colors.scss +2 -1
  181. data/app/pb_kits/playbook/utilities/deprecated.ts +73 -0
  182. data/app/pb_kits/playbook/utilities/emojiMask.ts +42 -0
  183. data/app/pb_kits/playbook/utilities/globalProps.ts +1 -0
  184. data/dist/chunks/_typeahead-CSCNg6cp.js +6 -0
  185. data/dist/chunks/lib-DxCgrqqG.js +29 -0
  186. data/dist/chunks/vendor.js +3 -3
  187. data/dist/menu.yml +16 -9
  188. data/dist/playbook-rails-react-bindings.js +1 -1
  189. data/dist/playbook-rails.js +1 -1
  190. data/dist/playbook.css +1 -1
  191. data/lib/playbook/forms/builder/collection_select_field.rb +9 -1
  192. data/lib/playbook/forms/builder/form_field_builder.rb +15 -2
  193. data/lib/playbook/forms/builder/select_field.rb +9 -1
  194. data/lib/playbook/forms/builder/time_picker_field.rb +24 -0
  195. data/lib/playbook/forms/builder/time_zone_select_field.rb +9 -1
  196. data/lib/playbook/forms/builder.rb +1 -0
  197. data/lib/playbook/pb_doc_helper.rb +3 -0
  198. data/lib/playbook/pb_kit_helper.rb +35 -0
  199. data/lib/playbook/version.rb +2 -2
  200. metadata +92 -4
  201. data/dist/chunks/_typeahead-BXM7QUuy.js +0 -6
  202. data/dist/chunks/lib-CgpqUb6l.js +0 -29
@@ -0,0 +1,822 @@
1
+ import React, { useState, useEffect, useRef, useMemo } from 'react'
2
+ import classnames from 'classnames'
3
+ import { buildAriaProps, buildCss, buildDataProps, buildHtmlProps } from '../utilities/props'
4
+ import { globalProps, GlobalProps } from '../utilities/globalProps'
5
+ import Caption from '../pb_caption/_caption'
6
+ import SelectableCard from '../pb_selectable_card/_selectable_card'
7
+ import TextInput from '../pb_text_input/_text_input'
8
+
9
+ import {
10
+ parseTime,
11
+ parseTimeToMinutes,
12
+ isTimeInRange as isTimeInRangeHelper,
13
+ isHourDisabled as isHourDisabledHelper,
14
+ isAnyAMTimeValid as isAnyAMTimeValidHelper,
15
+ isAnyPMTimeValid as isAnyPMTimeValidHelper,
16
+ getDisplayTime,
17
+ get24HourTime,
18
+ getTimeRangeErrorMessage,
19
+ getTimezoneText,
20
+ getHourConstraints,
21
+ processHourInput,
22
+ normalizeHourOnBlur,
23
+ processMinuteInput,
24
+ normalizeMinuteOnBlur,
25
+ convertTo24HourFormat,
26
+ convertTo12HourFormat,
27
+ generateHourOptions,
28
+ generateMinuteOptions,
29
+ getValidInitialMeridiem as getValidInitialMeridiemHelper,
30
+ TimeFormat,
31
+ ParsedTime,
32
+ } from './time_picker_helper'
33
+
34
+ type TimePickerProps = {
35
+ aria?: { [key: string]: string },
36
+ className?: string,
37
+ data?: { [key: string]: string },
38
+ defaultTime?: string,
39
+ disabled?: boolean,
40
+ error?: string,
41
+ htmlOptions?: { [key: string]: string | number | boolean | (() => void) | ((arg?: Event) => void) },
42
+ id?: string,
43
+ inputOptions?: { [key: string]: string | number | boolean | (() => void) | ((arg?: Event) => void) },
44
+ label?: string,
45
+ maxTime?: string,
46
+ minTime?: string,
47
+ name?: string,
48
+ onChange?: (time: string) => void,
49
+ onClose?: (time: string) => void,
50
+ required?: boolean,
51
+ showTimezone?: boolean,
52
+ timeFormat?: TimeFormat,
53
+ validationMessage?: string,
54
+ value?: string,
55
+ } & GlobalProps
56
+
57
+ const TimePicker = (props: TimePickerProps): JSX.Element => {
58
+ const {
59
+ aria = {},
60
+ className,
61
+ data = {},
62
+ defaultTime,
63
+ disabled = false,
64
+ error,
65
+ htmlOptions = {},
66
+ id,
67
+ inputOptions = {},
68
+ label = 'Time Picker',
69
+ maxTime,
70
+ minTime,
71
+ name,
72
+ onChange,
73
+ onClose,
74
+ required = false,
75
+ showTimezone = false,
76
+ timeFormat = 'AMPM',
77
+ validationMessage,
78
+ value,
79
+ } = props
80
+
81
+ // Form Validation Tracking for React Rendered Rails Kit
82
+ const [formSubmitted, setFormSubmitted] = useState(false)
83
+
84
+ const uniqueId = useMemo(() => {
85
+ return id || `time-picker-${Math.random().toString(36).substr(2, 9)}`
86
+ }, [id])
87
+
88
+ const fieldName = name || `${uniqueId}-time`
89
+
90
+ useEffect(() => {
91
+ const handleInvalid = (event: Event) => {
92
+ const target = event.target as HTMLInputElement
93
+ const timePickerContainer = target.closest('[data-pb-react-component="TimePicker"]')
94
+
95
+ if (timePickerContainer) {
96
+ const invalidInputName = target.name || target.getAttribute('name')
97
+ if (invalidInputName === fieldName) {
98
+ setFormSubmitted(true)
99
+ }
100
+ }
101
+ }
102
+ document.addEventListener('invalid', handleInvalid, true)
103
+
104
+ return () => {
105
+ document.removeEventListener('invalid', handleInvalid, true)
106
+ }
107
+ }, [fieldName])
108
+
109
+ // Min/Max Time Range Validation
110
+ const minTimeMinutes = parseTimeToMinutes(minTime)
111
+ const maxTimeMinutes = parseTimeToMinutes(maxTime)
112
+
113
+ const isTimeInRange = (h: number, m: number, mer?: 'AM' | 'PM'): boolean => {
114
+ return isTimeInRangeHelper(h, m, mer, timeFormat, minTimeMinutes, maxTimeMinutes)
115
+ }
116
+
117
+ const isHourDisabled = (h: number, mer?: 'AM' | 'PM'): boolean => {
118
+ return isHourDisabledHelper(h, mer, timeFormat, minTimeMinutes, maxTimeMinutes)
119
+ }
120
+
121
+ const isCurrentTimeValid = (h: number, m: number, mer: 'AM' | 'PM'): boolean => {
122
+ return isTimeInRange(h, m, mer)
123
+ }
124
+
125
+ const isAnyAMTimeValid = (): boolean => {
126
+ return isAnyAMTimeValidHelper(minTimeMinutes, maxTimeMinutes)
127
+ }
128
+
129
+ const isAnyPMTimeValid = (): boolean => {
130
+ return isAnyPMTimeValidHelper(minTimeMinutes, maxTimeMinutes)
131
+ }
132
+
133
+ // Wrapper for helper function with component's min/max context
134
+ const getValidInitialMeridiem = (parsedMeridiem: 'AM' | 'PM'): 'AM' | 'PM' => {
135
+ return getValidInitialMeridiemHelper(parsedMeridiem, minTimeMinutes, maxTimeMinutes)
136
+ }
137
+
138
+ const hasInitialValue = !!(value || defaultTime)
139
+ const initialTime = parseTime(value || defaultTime, timeFormat)
140
+ const validInitialMeridiem = getValidInitialMeridiem(initialTime.meridiem)
141
+ const [hour, setHour] = useState(initialTime.hour)
142
+ const [minute, setMinute] = useState(initialTime.minute)
143
+ const [meridiem, setMeridiem] = useState<'AM' | 'PM'>(validInitialMeridiem)
144
+ const [hasSelectedTime, setHasSelectedTime] = useState(hasInitialValue)
145
+ const [hourInputValue, setHourInputValue] = useState<string>(initialTime.hour.toString())
146
+ const [minuteInputValue, setMinuteInputValue] = useState<string>(initialTime.minute.toString().padStart(2, '0'))
147
+
148
+ // Update hour input value format based on timeFormat changes
149
+ const prevTimeFormatRef = useRef(timeFormat)
150
+ const isInitialMountRef = useRef(true)
151
+ useEffect(() => {
152
+ if (isInitialMountRef.current) {
153
+ isInitialMountRef.current = false
154
+ prevTimeFormatRef.current = timeFormat
155
+ return
156
+ }
157
+
158
+ if (prevTimeFormatRef.current === timeFormat) return
159
+ prevTimeFormatRef.current = timeFormat
160
+
161
+ if (timeFormat === '24hour') {
162
+ const result = convertTo24HourFormat(hour, meridiem)
163
+ setHour(result.hour)
164
+ setHourInputValue(result.hour.toString())
165
+ if (result.meridiem !== meridiem) {
166
+ setMeridiem(result.meridiem)
167
+ }
168
+ } else {
169
+ const result = convertTo12HourFormat(hour, meridiem)
170
+ setHour(result.hour)
171
+ setHourInputValue(result.hour.toString())
172
+ if (result.meridiem !== meridiem) {
173
+ setMeridiem(result.meridiem)
174
+ }
175
+ }
176
+ // eslint-disable-next-line react-hooks/exhaustive-deps
177
+ }, [timeFormat])
178
+
179
+ const hourInputRef = useRef<HTMLInputElement>(null)
180
+ const minuteInputRef = useRef<HTMLInputElement>(null)
181
+ const amInputRef = useRef<HTMLInputElement | null>(null)
182
+ const pmInputRef = useRef<HTMLInputElement | null>(null)
183
+ const timePickerWrapperRef = useRef<HTMLDivElement>(null)
184
+ const hourDropdownRef = useRef<HTMLDivElement>(null)
185
+ const minuteDropdownRef = useRef<HTMLDivElement>(null)
186
+ const [showDropdown, setShowDropdown] = useState(false)
187
+ const [showHourDropdown, setShowHourDropdown] = useState(false)
188
+ const [showMinuteDropdown, setShowMinuteDropdown] = useState(false)
189
+
190
+ // Input dropdown scrolling
191
+ const scrollDropdownToSelected = (dropdownRef: React.RefObject<HTMLDivElement>) => {
192
+ if (dropdownRef.current) {
193
+ const selectedOption = dropdownRef.current.querySelector('.selected') as HTMLElement
194
+ if (selectedOption) {
195
+ const dropdown = dropdownRef.current
196
+ const dropdownHeight = dropdown.clientHeight
197
+ const optionTop = selectedOption.offsetTop
198
+ const optionHeight = selectedOption.clientHeight
199
+ // Center the selected option in the dropdown
200
+ dropdown.scrollTop = optionTop - (dropdownHeight / 2) + (optionHeight / 2)
201
+ }
202
+ }
203
+ }
204
+
205
+ useEffect(() => {
206
+ if (showHourDropdown) {
207
+ scrollDropdownToSelected(hourDropdownRef)
208
+ }
209
+ }, [showHourDropdown])
210
+
211
+ useEffect(() => {
212
+ if (showMinuteDropdown) {
213
+ scrollDropdownToSelected(minuteDropdownRef)
214
+ }
215
+ }, [showMinuteDropdown])
216
+
217
+ useEffect(() => {
218
+ if (showHourDropdown) {
219
+ setTimeout(() => scrollDropdownToSelected(hourDropdownRef), 0)
220
+ }
221
+ // eslint-disable-next-line react-hooks/exhaustive-deps
222
+ }, [hour])
223
+
224
+ useEffect(() => {
225
+ if (showMinuteDropdown) {
226
+ setTimeout(() => scrollDropdownToSelected(minuteDropdownRef), 0)
227
+ }
228
+ // eslint-disable-next-line react-hooks/exhaustive-deps
229
+ }, [minute])
230
+
231
+ const [displayValue, setDisplayValue] = useState(
232
+ hasInitialValue ? getDisplayTime(hour, minute, meridiem, timeFormat) : ''
233
+ )
234
+
235
+ // Clearing Input and Validation Code
236
+ // Track the last valid time for reverting invalid selections
237
+ const [lastValidTime, setLastValidTime] = useState<ParsedTime | null>(
238
+ hasInitialValue && isTimeInRange(initialTime.hour, initialTime.minute, initialTime.meridiem)
239
+ ? initialTime
240
+ : null
241
+ )
242
+
243
+ // Clear the time picker value completely
244
+ const clearTimePicker = () => {
245
+ setHasSelectedTime(false)
246
+ setLastValidTime(null)
247
+ setDisplayValue('')
248
+ const defaultState = parseTime(undefined, timeFormat)
249
+ const validMeridiem = getValidInitialMeridiem(defaultState.meridiem)
250
+ setHour(defaultState.hour)
251
+ setMinute(defaultState.minute)
252
+ setMeridiem(validMeridiem)
253
+ setHourInputValue(defaultState.hour.toString())
254
+ setMinuteInputValue(defaultState.minute.toString().padStart(2, '0'))
255
+ if (onChange) {
256
+ onChange('')
257
+ }
258
+ }
259
+
260
+ // Close dropdown and handle validation
261
+ const closeDropdown = (skipValidation = false) => {
262
+ setShowHourDropdown(false)
263
+ setShowMinuteDropdown(false)
264
+
265
+ // If user hasn't selected anything, just close the dropdown
266
+ if (!hasSelectedTime) {
267
+ setShowDropdown(false)
268
+ return
269
+ }
270
+
271
+ const currentTimeValid = skipValidation || isCurrentTimeValid(hour, minute, meridiem)
272
+
273
+ if (currentTimeValid) {
274
+ // Valid time - save it
275
+ const timeString = get24HourTime(hour, minute, meridiem, timeFormat)
276
+ setDisplayValue(getDisplayTime(hour, minute, meridiem, timeFormat))
277
+ setLastValidTime({ hour, minute, meridiem })
278
+ setShowDropdown(false)
279
+ if (onClose) {
280
+ onClose(timeString)
281
+ }
282
+ } else {
283
+ // Invalid time - revert to last valid time or reset to defaults
284
+ if (lastValidTime) {
285
+ setHour(lastValidTime.hour)
286
+ setMinute(lastValidTime.minute)
287
+ setMeridiem(lastValidTime.meridiem)
288
+ setHourInputValue(lastValidTime.hour.toString())
289
+ setMinuteInputValue(lastValidTime.minute.toString().padStart(2, '0'))
290
+ setDisplayValue(getDisplayTime(lastValidTime.hour, lastValidTime.minute, lastValidTime.meridiem, timeFormat))
291
+ } else {
292
+ // No valid time ever selected - reset to default state
293
+ const defaultState = parseTime(undefined, timeFormat)
294
+ const validMeridiem = getValidInitialMeridiem(defaultState.meridiem)
295
+ setHour(defaultState.hour)
296
+ setMinute(defaultState.minute)
297
+ setMeridiem(validMeridiem)
298
+ setHourInputValue(defaultState.hour.toString())
299
+ setMinuteInputValue(defaultState.minute.toString().padStart(2, '0'))
300
+ setDisplayValue('')
301
+ setHasSelectedTime(false)
302
+ }
303
+ setShowDropdown(false)
304
+ }
305
+ }
306
+
307
+ // Handle controlled component updates
308
+ useEffect(() => {
309
+ if (value !== undefined) {
310
+ const parsed = parseTime(value, timeFormat)
311
+ setHour(parsed.hour)
312
+ setMinute(parsed.minute)
313
+ setMeridiem(parsed.meridiem)
314
+ setHourInputValue(parsed.hour.toString())
315
+ setMinuteInputValue(parsed.minute.toString().padStart(2, '0'))
316
+ setDisplayValue(getDisplayTime(parsed.hour, parsed.minute, parsed.meridiem, timeFormat))
317
+ setHasSelectedTime(true)
318
+ }
319
+ // eslint-disable-next-line react-hooks/exhaustive-deps
320
+ }, [value])
321
+
322
+ // Re-parse when timeFormat changes if we have a defaultTime
323
+ const prevTimeFormatRef3 = useRef(timeFormat)
324
+ const isInitialMountRef3 = useRef(true)
325
+ useEffect(() => {
326
+ if (isInitialMountRef3.current) {
327
+ isInitialMountRef3.current = false
328
+ prevTimeFormatRef3.current = timeFormat
329
+ return
330
+ }
331
+
332
+ if (prevTimeFormatRef3.current === timeFormat) return
333
+ prevTimeFormatRef3.current = timeFormat
334
+
335
+ if (defaultTime && !value) {
336
+ const parsed = parseTime(defaultTime, timeFormat)
337
+ setHour(parsed.hour)
338
+ setMinute(parsed.minute)
339
+ setMeridiem(parsed.meridiem)
340
+ setHourInputValue(parsed.hour.toString())
341
+ setMinuteInputValue(parsed.minute.toString().padStart(2, '0'))
342
+ setDisplayValue(getDisplayTime(parsed.hour, parsed.minute, parsed.meridiem, timeFormat))
343
+ }
344
+ // eslint-disable-next-line react-hooks/exhaustive-deps
345
+ }, [timeFormat])
346
+
347
+ useEffect(() => {
348
+ if (hasSelectedTime) {
349
+ setDisplayValue(getDisplayTime(hour, minute, meridiem, timeFormat))
350
+ }
351
+ // eslint-disable-next-line react-hooks/exhaustive-deps
352
+ }, [hour, minute, meridiem, hasSelectedTime, timeFormat])
353
+
354
+ const handleHourChange = (e: React.ChangeEvent<HTMLInputElement>) => {
355
+ const rawValue = e.target.value
356
+ const result = processHourInput(rawValue, timeFormat)
357
+
358
+ if (!result.isValid) return
359
+
360
+ setHourInputValue(result.value)
361
+
362
+ if (result.hour !== null) {
363
+ setHour(result.hour)
364
+ setHasSelectedTime(true)
365
+ const timeString = get24HourTime(result.hour, minute, meridiem, timeFormat)
366
+ if (onChange) {
367
+ onChange(timeString)
368
+ }
369
+ }
370
+ }
371
+
372
+ const handleHourBlur = () => {
373
+ const result = normalizeHourOnBlur(hourInputValue, hour, timeFormat)
374
+ setHour(result.hour)
375
+ setHourInputValue(result.displayValue)
376
+ }
377
+
378
+ const handleMinuteChange = (e: React.ChangeEvent<HTMLInputElement>) => {
379
+ const rawValue = e.target.value
380
+ const result = processMinuteInput(rawValue)
381
+
382
+ if (!result.isValid) return
383
+
384
+ setMinuteInputValue(result.value)
385
+
386
+ if (result.minute !== null) {
387
+ setMinute(result.minute)
388
+ setHasSelectedTime(true)
389
+ const timeString = get24HourTime(hour, result.minute, meridiem, timeFormat)
390
+ if (onChange) {
391
+ onChange(timeString)
392
+ }
393
+ }
394
+ }
395
+
396
+ const handleMinuteBlur = () => {
397
+ const result = normalizeMinuteOnBlur(minuteInputValue, minute)
398
+ setMinute(result.minute)
399
+ setMinuteInputValue(result.displayValue)
400
+ }
401
+
402
+ const handleHourOptionClick = (h: number) => {
403
+ setHour(h)
404
+ setHourInputValue(h.toString())
405
+ setHasSelectedTime(true)
406
+ setShowHourDropdown(false)
407
+ const timeString = get24HourTime(h, minute, meridiem, timeFormat)
408
+ if (onChange) {
409
+ onChange(timeString)
410
+ }
411
+ }
412
+
413
+ const handleMinuteOptionClick = (m: number) => {
414
+ setMinute(m)
415
+ setMinuteInputValue(m.toString().padStart(2, '0'))
416
+ setHasSelectedTime(true)
417
+ setShowMinuteDropdown(false)
418
+ const timeString = get24HourTime(hour, m, meridiem, timeFormat)
419
+ if (onChange) {
420
+ onChange(timeString)
421
+ }
422
+ }
423
+
424
+ const handleMeridiemChange = (mer: 'AM' | 'PM') => {
425
+ setMeridiem(mer)
426
+ setHasSelectedTime(true)
427
+ const timeString = get24HourTime(hour, minute, mer, timeFormat)
428
+ if (onChange) {
429
+ onChange(timeString)
430
+ }
431
+ }
432
+
433
+ const handleInputClick = () => {
434
+ setShowDropdown(true)
435
+ }
436
+
437
+ const handleInputFocus = () => {
438
+ setShowDropdown(true)
439
+ }
440
+
441
+ const handleInputKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
442
+ if (e.key === 'Enter') {
443
+ e.preventDefault()
444
+ closeDropdown()
445
+ } else if (e.key === 'Escape') {
446
+ e.preventDefault()
447
+ closeDropdown()
448
+ } else if (e.key === 'Tab' && !e.shiftKey && showDropdown) {
449
+ e.preventDefault()
450
+ hourInputRef.current?.focus()
451
+ } else if (e.key === 'Tab' && e.shiftKey) {
452
+ // Allow shift+tab to go to previous element
453
+ return
454
+ } else if (e.key === 'Backspace' || e.key === 'Delete') {
455
+ // Allow clearing the input
456
+ e.preventDefault()
457
+ clearTimePicker()
458
+ } else if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
459
+ // Open dropdown on arrow keys
460
+ e.preventDefault()
461
+ if (!showDropdown) {
462
+ setShowDropdown(true)
463
+ }
464
+ } else {
465
+ // Prevent typing in main input - alternative to readonly that allows for validation to occur
466
+ e.preventDefault()
467
+ }
468
+ }
469
+
470
+ const handleHourKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
471
+ if (e.key === 'Tab') {
472
+ if (!e.shiftKey) {
473
+ e.preventDefault()
474
+ setShowHourDropdown(false)
475
+ minuteInputRef.current?.focus()
476
+ }
477
+ } else if (e.key === 'Enter') {
478
+ e.preventDefault()
479
+ setShowHourDropdown(false)
480
+ closeDropdown()
481
+ } else if (e.key === 'Escape') {
482
+ e.preventDefault()
483
+ setShowHourDropdown(false)
484
+ closeDropdown()
485
+ }
486
+ }
487
+
488
+ const handleMinuteKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
489
+ if (e.key === 'Tab' && e.shiftKey) {
490
+ e.preventDefault()
491
+ setShowMinuteDropdown(false)
492
+ hourInputRef.current?.focus()
493
+ } else if (e.key === 'Tab') {
494
+ if (!e.shiftKey) {
495
+ e.preventDefault()
496
+ setShowMinuteDropdown(false)
497
+ if (timeFormat === '24hour') {
498
+ closeDropdown()
499
+ } else {
500
+ // Tab to the currently selected meridiem
501
+ if (meridiem === 'AM') {
502
+ amInputRef.current?.focus()
503
+ } else {
504
+ pmInputRef.current?.focus()
505
+ }
506
+ }
507
+ }
508
+ } else if (e.key === 'Enter') {
509
+ e.preventDefault()
510
+ setShowMinuteDropdown(false)
511
+ closeDropdown()
512
+ } else if (e.key === 'Escape') {
513
+ e.preventDefault()
514
+ setShowMinuteDropdown(false)
515
+ closeDropdown()
516
+ }
517
+ }
518
+
519
+ // Shared handler for AM/PM - arrow keys toggle between them
520
+ const handleMeridiemKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
521
+ if (e.key === 'Tab' && e.shiftKey) {
522
+ e.preventDefault()
523
+ minuteInputRef.current?.focus()
524
+ } else if (e.key === 'Tab' && !e.shiftKey) {
525
+ // Tab out of the component - let it close
526
+ e.preventDefault()
527
+ closeDropdown()
528
+ } else if (e.key === 'ArrowLeft' || e.key === 'ArrowUp') {
529
+ e.preventDefault()
530
+ // Toggle to AM and focus it
531
+ if (meridiem !== 'AM' && isAnyAMTimeValid()) {
532
+ handleMeridiemChange('AM')
533
+ amInputRef.current?.focus()
534
+ }
535
+ } else if (e.key === 'ArrowRight' || e.key === 'ArrowDown') {
536
+ e.preventDefault()
537
+ // Toggle to PM and focus it
538
+ if (meridiem !== 'PM' && isAnyPMTimeValid()) {
539
+ handleMeridiemChange('PM')
540
+ pmInputRef.current?.focus()
541
+ }
542
+ } else if (e.key === 'Enter' || e.key === ' ') {
543
+ // Enter or Space confirms selection and closes
544
+ e.preventDefault()
545
+ closeDropdown()
546
+ } else if (e.key === 'Escape') {
547
+ e.preventDefault()
548
+ closeDropdown()
549
+ }
550
+ }
551
+
552
+ // Set up keyboard handlers for AM/PM inputs
553
+ useEffect(() => {
554
+ if (showDropdown && timeFormat === 'AMPM') {
555
+ const amInput = document.getElementById(`${uniqueId}-am`) as HTMLInputElement
556
+ const pmInput = document.getElementById(`${uniqueId}-pm`) as HTMLInputElement
557
+
558
+ const handleKeyDownEvent = (e: KeyboardEvent) => {
559
+ const reactEvent = e as unknown as React.KeyboardEvent<HTMLInputElement>
560
+ handleMeridiemKeyDown(reactEvent)
561
+ }
562
+
563
+ if (amInput) {
564
+ (amInputRef as React.MutableRefObject<HTMLInputElement | null>).current = amInput
565
+ amInput.addEventListener('keydown', handleKeyDownEvent)
566
+ }
567
+
568
+ if (pmInput) {
569
+ (pmInputRef as React.MutableRefObject<HTMLInputElement | null>).current = pmInput
570
+ pmInput.addEventListener('keydown', handleKeyDownEvent)
571
+ }
572
+
573
+ return () => {
574
+ if (amInput) amInput.removeEventListener('keydown', handleKeyDownEvent)
575
+ if (pmInput) pmInput.removeEventListener('keydown', handleKeyDownEvent)
576
+ }
577
+ }
578
+ // eslint-disable-next-line react-hooks/exhaustive-deps
579
+ }, [showDropdown, timeFormat, uniqueId, meridiem])
580
+
581
+ useEffect(() => {
582
+ const handleClickOutside = (event: MouseEvent) => {
583
+ if (timePickerWrapperRef.current && !timePickerWrapperRef.current.contains(event.target as Node)) {
584
+ closeDropdown()
585
+ }
586
+ }
587
+
588
+ if (showDropdown) {
589
+ document.addEventListener('mousedown', handleClickOutside)
590
+ return () => {
591
+ document.removeEventListener('mousedown', handleClickOutside)
592
+ }
593
+ }
594
+ // eslint-disable-next-line react-hooks/exhaustive-deps
595
+ }, [showDropdown, hour, minute, meridiem, onClose, lastValidTime, minTime, maxTime])
596
+
597
+ const ariaProps = buildAriaProps(aria)
598
+ const dataProps = buildDataProps(data)
599
+ const htmlProps = buildHtmlProps(htmlOptions)
600
+ const inputHtmlProps = buildHtmlProps(inputOptions)
601
+
602
+ const shouldShowValidationError = required && formSubmitted && !hasSelectedTime
603
+ const errorDisplay = error || (shouldShowValidationError ? (validationMessage || "Please fill out this field.") : "")
604
+
605
+ const classes = classnames(
606
+ buildCss('pb_time_picker'),
607
+ globalProps(props),
608
+ errorDisplay ? 'error' : null,
609
+ disabled ? 'disabled' : null,
610
+ className
611
+ )
612
+
613
+ // Get hour constraints for the input
614
+ const { maxHour, minHour } = getHourConstraints(timeFormat)
615
+
616
+ return (
617
+ <div
618
+ {...ariaProps}
619
+ {...dataProps}
620
+ {...htmlProps}
621
+ className={classes}
622
+ id={uniqueId}
623
+ ref={timePickerWrapperRef}
624
+ style={{ position: 'relative' }}
625
+ >
626
+ {label && (
627
+ <label htmlFor={`${uniqueId}-input`}>
628
+ <Caption
629
+ className="pb_time_picker_kit_label"
630
+ marginBottom="xs"
631
+ size="md"
632
+ text={label}
633
+ />
634
+ </label>
635
+ )}
636
+ <div className="time_picker_wrapper">
637
+ <TextInput
638
+ addOn={{ icon: 'clock', alignment: 'right', border: true }}
639
+ cursor="pointer"
640
+ disabled={disabled}
641
+ error={errorDisplay}
642
+ id={`${uniqueId}-input`}
643
+ label=""
644
+ name={fieldName}
645
+ onClick={disabled ? undefined : handleInputClick}
646
+ onFocus={disabled ? undefined : handleInputFocus}
647
+ placeholder="Select Time"
648
+ required={required}
649
+ type="text"
650
+ value={displayValue}
651
+ >
652
+ <input
653
+ autoComplete="off"
654
+ disabled={disabled}
655
+ id={`${uniqueId}-input`}
656
+ name={fieldName}
657
+ onChange={() => { /* onChange handled via dropdown selection */ }}
658
+ onClick={disabled ? undefined : handleInputClick}
659
+ onFocus={disabled ? undefined : handleInputFocus}
660
+ onKeyDown={disabled ? undefined : handleInputKeyDown}
661
+ placeholder="Select Time"
662
+ required={required}
663
+ style={{ caretColor: 'transparent' }}
664
+ type="text"
665
+ value={displayValue}
666
+ {...inputHtmlProps}
667
+ />
668
+ </TextInput>
669
+
670
+ {showDropdown && !disabled && (
671
+ <div className={`pb_time_picker_container ${timeFormat === '24hour' ? 'pb_time_picker_container_24hour' : ''}`}>
672
+ <div className="pb_time_selection">
673
+ <div className="time_input_wrapper">
674
+ <label htmlFor={`${uniqueId}-hour`}>
675
+ <Caption
676
+ className="time_input_label"
677
+ size="sm"
678
+ text="Hour"
679
+ />
680
+ </label>
681
+ <input
682
+ className={`time_input time-hour ${hasSelectedTime && !isCurrentTimeValid(hour, minute, meridiem) ? 'invalid' : ''}`}
683
+ id={`${uniqueId}-hour`}
684
+ inputMode="numeric"
685
+ max={maxHour}
686
+ maxLength={2}
687
+ min={minHour}
688
+ name={`${uniqueId}-hour`}
689
+ onBlur={handleHourBlur}
690
+ onChange={handleHourChange}
691
+ onClick={() => { setShowHourDropdown(!showHourDropdown); setShowMinuteDropdown(false) }}
692
+ onKeyDown={handleHourKeyDown}
693
+ pattern="[0-9]*"
694
+ ref={hourInputRef}
695
+ step={1}
696
+ tabIndex={0}
697
+ type="number"
698
+ value={hourInputValue}
699
+ />
700
+ {showHourDropdown && (
701
+ <div
702
+ className="time_dropdown"
703
+ ref={hourDropdownRef}
704
+ >
705
+ {generateHourOptions(timeFormat).map((h) => (
706
+ <div
707
+ className={`time_dropdown_option ${hour === h ? 'selected' : ''}`}
708
+ key={h}
709
+ onClick={() => handleHourOptionClick(h)}
710
+ >
711
+ {timeFormat === '24hour' ? h.toString().padStart(2, '0') : h}
712
+ </div>
713
+ ))}
714
+ </div>
715
+ )}
716
+ </div>
717
+ <span className="time-separator">{':'}</span>
718
+ <div className="time_input_wrapper">
719
+ <label htmlFor={`${uniqueId}-minute`}>
720
+ <Caption
721
+ className="time_input_label"
722
+ size="sm"
723
+ text="Minute"
724
+ />
725
+ </label>
726
+ <input
727
+ className={`time_input time-minute ${hasSelectedTime && !isCurrentTimeValid(hour, minute, meridiem) ? 'invalid' : ''}`}
728
+ id={`${uniqueId}-minute`}
729
+ inputMode="numeric"
730
+ max={59}
731
+ maxLength={2}
732
+ min={0}
733
+ name={`${uniqueId}-minute`}
734
+ onBlur={handleMinuteBlur}
735
+ onChange={handleMinuteChange}
736
+ onClick={() => { setShowMinuteDropdown(!showMinuteDropdown); setShowHourDropdown(false) }}
737
+ onKeyDown={handleMinuteKeyDown}
738
+ pattern="[0-9]*"
739
+ ref={minuteInputRef}
740
+ step={1}
741
+ tabIndex={0}
742
+ type="number"
743
+ value={minuteInputValue}
744
+ />
745
+ {showMinuteDropdown && (
746
+ <div
747
+ className="time_dropdown"
748
+ ref={minuteDropdownRef}
749
+ >
750
+ {generateMinuteOptions().map((m) => (
751
+ <div
752
+ className={`time_dropdown_option ${minute === m ? 'selected' : ''}`}
753
+ key={m}
754
+ onClick={() => handleMinuteOptionClick(m)}
755
+ >
756
+ {m.toString().padStart(2, '0')}
757
+ </div>
758
+ ))}
759
+ </div>
760
+ )}
761
+ </div>
762
+ {timeFormat === 'AMPM' && (
763
+ <div className="meridiem">
764
+ <Caption
765
+ className="time_input_label"
766
+ size="sm"
767
+ text="Period"
768
+ />
769
+ <div className="pb_form_group_kit">
770
+ <SelectableCard
771
+ checked={meridiem === 'AM'}
772
+ className={!isAnyAMTimeValid() ? 'disabled_meridiem' : ''}
773
+ disabled={!isAnyAMTimeValid()}
774
+ inputId={`${uniqueId}-am`}
775
+ multi={false}
776
+ name={`${uniqueId}-meridiem`}
777
+ onChange={() => handleMeridiemChange('AM')}
778
+ text="AM"
779
+ value="AM"
780
+ />
781
+ <SelectableCard
782
+ checked={meridiem === 'PM'}
783
+ className={!isAnyPMTimeValid() ? 'disabled_meridiem' : ''}
784
+ disabled={!isAnyPMTimeValid()}
785
+ inputId={`${uniqueId}-pm`}
786
+ multi={false}
787
+ name={`${uniqueId}-meridiem`}
788
+ onChange={() => handleMeridiemChange('PM')}
789
+ text="PM"
790
+ value="PM"
791
+ />
792
+ </div>
793
+ </div>
794
+ )}
795
+ {/* Show validation error in dropdownwhen time is out of range */}
796
+ {hasSelectedTime && !isCurrentTimeValid(hour, minute, meridiem) && (
797
+ <div className="time_range_error">
798
+ <Caption
799
+ className="time_range_error_text"
800
+ marginTop="sm"
801
+ size="xs"
802
+ text={getTimeRangeErrorMessage(minTime, maxTime, timeFormat)}
803
+ />
804
+ </div>
805
+ )}
806
+ {showTimezone && (
807
+ <Caption
808
+ lineHeight="tight"
809
+ marginTop="sm"
810
+ size="xs"
811
+ text={getTimezoneText()}
812
+ />
813
+ )}
814
+ </div>
815
+ </div>
816
+ )}
817
+ </div>
818
+ </div>
819
+ )
820
+ }
821
+
822
+ export default TimePicker