playbook_ui 16.2.0.pre.rc.0 → 16.2.0.pre.rc.2

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 (89) hide show
  1. checksums.yaml +4 -4
  2. data/app/pb_kits/playbook/pb_advanced_table/Components/RegularTableView.tsx +12 -2
  3. data/app/pb_kits/playbook/pb_advanced_table/advanced_table.test.jsx +33 -0
  4. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_column_styling_background_custom.jsx +71 -0
  5. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_column_styling_background_custom.md +4 -0
  6. data/app/pb_kits/playbook/pb_advanced_table/docs/example.yml +1 -0
  7. data/app/pb_kits/playbook/pb_advanced_table/docs/index.js +2 -1
  8. data/app/pb_kits/playbook/pb_checkbox/_checkbox.scss +1 -1
  9. data/app/pb_kits/playbook/pb_checkbox/_checkbox.tsx +17 -0
  10. data/app/pb_kits/playbook/pb_checkbox/checkbox.html.erb +10 -1
  11. data/app/pb_kits/playbook/pb_checkbox/checkbox.rb +2 -0
  12. data/app/pb_kits/playbook/pb_checkbox/docs/_checkbox_required_indicator.html.erb +6 -0
  13. data/app/pb_kits/playbook/pb_checkbox/docs/_checkbox_required_indicator.jsx +17 -0
  14. data/app/pb_kits/playbook/pb_checkbox/docs/_checkbox_required_indicator.md +3 -0
  15. data/app/pb_kits/playbook/pb_checkbox/docs/example.yml +2 -0
  16. data/app/pb_kits/playbook/pb_checkbox/docs/index.js +1 -0
  17. data/app/pb_kits/playbook/pb_date_picker/_date_picker.tsx +14 -5
  18. data/app/pb_kits/playbook/pb_date_picker/docs/_date_picker_default.md +1 -0
  19. data/app/pb_kits/playbook/pb_dropdown/_dropdown.tsx +46 -11
  20. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_label.html.erb +6 -3
  21. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_label.jsx +1 -0
  22. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_label.md +3 -1
  23. data/app/pb_kits/playbook/pb_dropdown/dropdown.html.erb +10 -4
  24. data/app/pb_kits/playbook/pb_dropdown/dropdown.rb +9 -0
  25. data/app/pb_kits/playbook/pb_dropdown/dropdown_trigger.html.erb +7 -2
  26. data/app/pb_kits/playbook/pb_dropdown/dropdown_trigger.rb +4 -0
  27. data/app/pb_kits/playbook/pb_dropdown/index.js +125 -73
  28. data/app/pb_kits/playbook/pb_dropdown/subcomponents/DropdownTrigger.tsx +16 -0
  29. data/app/pb_kits/playbook/pb_dropdown/utilities/clickOutsideHelper.tsx +6 -0
  30. data/app/pb_kits/playbook/pb_form/docs/_form_with_required_indicator.html.erb +6 -3
  31. data/app/pb_kits/playbook/pb_form/pb_form_validation.js +9 -2
  32. data/app/pb_kits/playbook/pb_multi_level_select/_multi_level_select.scss +7 -0
  33. data/app/pb_kits/playbook/pb_multi_level_select/_multi_level_select.tsx +638 -549
  34. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_label.html.erb +3 -3
  35. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_label.jsx +4 -7
  36. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_label.md +3 -0
  37. data/app/pb_kits/playbook/pb_multi_level_select/multi_level_select.test.jsx +4 -4
  38. data/app/pb_kits/playbook/pb_passphrase/_passphrase.tsx +20 -2
  39. data/app/pb_kits/playbook/pb_rich_text_editor/_rich_text_editor.tsx +51 -16
  40. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_advanced_label.jsx +44 -0
  41. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_advanced_label.md +1 -0
  42. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_advanced_required_indicator.jsx +1 -0
  43. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_label.jsx +28 -0
  44. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_label.md +1 -0
  45. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_required_indicator.jsx +1 -0
  46. data/app/pb_kits/playbook/pb_rich_text_editor/docs/example.yml +2 -0
  47. data/app/pb_kits/playbook/pb_rich_text_editor/docs/index.js +2 -0
  48. data/app/pb_kits/playbook/pb_table/index.ts +29 -27
  49. data/app/pb_kits/playbook/pb_text_input/text_input.html.erb +10 -10
  50. data/app/pb_kits/playbook/pb_textarea/_textarea.tsx +10 -0
  51. data/app/pb_kits/playbook/pb_textarea/docs/_textarea_default.html.erb +3 -3
  52. data/app/pb_kits/playbook/pb_textarea/docs/_textarea_default.jsx +3 -0
  53. data/app/pb_kits/playbook/pb_textarea/docs/_textarea_default.md +1 -0
  54. data/app/pb_kits/playbook/pb_textarea/textarea.html.erb +25 -9
  55. data/app/pb_kits/playbook/pb_textarea/textarea.rb +7 -1
  56. data/app/pb_kits/playbook/pb_time_picker/_time_picker.tsx +97 -11
  57. data/app/pb_kits/playbook/pb_time_picker/docs/_time_picker_on_handler.jsx +5 -2
  58. data/app/pb_kits/playbook/pb_time_picker/docs/_time_picker_required_indicator.html.erb +6 -0
  59. data/app/pb_kits/playbook/pb_time_picker/docs/_time_picker_required_indicator.jsx +16 -0
  60. data/app/pb_kits/playbook/pb_time_picker/docs/_time_picker_required_indicator.md +3 -0
  61. data/app/pb_kits/playbook/pb_time_picker/docs/example.yml +2 -0
  62. data/app/pb_kits/playbook/pb_time_picker/docs/index.js +1 -0
  63. data/app/pb_kits/playbook/pb_time_picker/time_picker.rb +3 -0
  64. data/app/pb_kits/playbook/pb_time_picker/time_picker.test.jsx +47 -1
  65. data/app/pb_kits/playbook/pb_typeahead/_typeahead.tsx +410 -323
  66. data/app/pb_kits/playbook/pb_typeahead/components/Control.tsx +2 -0
  67. data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_required_indicator.html.erb +16 -0
  68. data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_required_indicator.jsx +23 -0
  69. data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_required_indicator.md +3 -0
  70. data/app/pb_kits/playbook/pb_typeahead/docs/example.yml +2 -0
  71. data/app/pb_kits/playbook/pb_typeahead/docs/index.js +22 -21
  72. data/app/pb_kits/playbook/pb_typeahead/typeahead.html.erb +3 -2
  73. data/app/pb_kits/playbook/pb_typeahead/typeahead.rb +3 -1
  74. data/dist/chunks/{_pb_line_graph-BgKF_zz1.js → _pb_line_graph-DuJNCf7N.js} +1 -1
  75. data/dist/chunks/_typeahead-BKSzddAX.js +1 -0
  76. data/dist/chunks/{globalProps-BhVYCqRf.js → globalProps-Bc-FVsRt.js} +1 -1
  77. data/dist/chunks/lib-BwX82vim.js +29 -0
  78. data/dist/chunks/vendor.js +3 -3
  79. data/dist/menu.yml +2 -2
  80. data/dist/playbook-rails-react-bindings.js +1 -1
  81. data/dist/playbook-rails.js +1 -1
  82. data/dist/playbook.css +1 -1
  83. data/lib/playbook/forms/builder/form_field_builder.rb +1 -1
  84. data/lib/playbook/forms/builder/typeahead_field.rb +15 -1
  85. data/lib/playbook/forms/builder.rb +2 -2
  86. data/lib/playbook/version.rb +1 -1
  87. metadata +24 -6
  88. data/dist/chunks/_typeahead-Bfy-4mll.js +0 -1
  89. data/dist/chunks/lib-DD34ZrWL.js +0 -29
@@ -5,12 +5,12 @@ import { globalProps, GlobalProps } from '../utilities/globalProps'
5
5
  import Caption from '../pb_caption/_caption'
6
6
  import SelectableCard from '../pb_selectable_card/_selectable_card'
7
7
  import TextInput from '../pb_text_input/_text_input'
8
+ import colors from '../tokens/exports/_colors.module.scss'
8
9
 
9
10
  import {
10
11
  parseTime,
11
12
  parseTimeToMinutes,
12
13
  isTimeInRange as isTimeInRangeHelper,
13
- isHourDisabled as isHourDisabledHelper,
14
14
  isAnyAMTimeValid as isAnyAMTimeValidHelper,
15
15
  isAnyPMTimeValid as isAnyPMTimeValidHelper,
16
16
  getDisplayTime,
@@ -48,6 +48,7 @@ type TimePickerProps = {
48
48
  onChange?: (time: string) => void,
49
49
  onClose?: (time: string) => void,
50
50
  required?: boolean,
51
+ requiredIndicator?: boolean,
51
52
  showTimezone?: boolean,
52
53
  timeFormat?: TimeFormat,
53
54
  validationMessage?: string,
@@ -72,6 +73,7 @@ const TimePicker = (props: TimePickerProps): JSX.Element => {
72
73
  onChange,
73
74
  onClose,
74
75
  required = false,
76
+ requiredIndicator = false,
75
77
  showTimezone = false,
76
78
  timeFormat = 'AMPM',
77
79
  validationMessage,
@@ -114,10 +116,6 @@ const TimePicker = (props: TimePickerProps): JSX.Element => {
114
116
  return isTimeInRangeHelper(h, m, mer, timeFormat, minTimeMinutes, maxTimeMinutes)
115
117
  }
116
118
 
117
- const isHourDisabled = (h: number, mer?: 'AM' | 'PM'): boolean => {
118
- return isHourDisabledHelper(h, mer, timeFormat, minTimeMinutes, maxTimeMinutes)
119
- }
120
-
121
119
  const isCurrentTimeValid = (h: number, m: number, mer: 'AM' | 'PM'): boolean => {
122
120
  return isTimeInRange(h, m, mer)
123
121
  }
@@ -187,6 +185,28 @@ const TimePicker = (props: TimePickerProps): JSX.Element => {
187
185
  const [showHourDropdown, setShowHourDropdown] = useState(false)
188
186
  const [showMinuteDropdown, setShowMinuteDropdown] = useState(false)
189
187
 
188
+ // Clicking the clock add-on opens the dropdown
189
+ useEffect(() => {
190
+ if (disabled) return
191
+
192
+ const addOnCard = document.querySelector(`#${uniqueId}-input`)?.closest('.text_input_wrapper_add_on')?.querySelector('.add-on-card') as HTMLElement
193
+
194
+ if (addOnCard) {
195
+ const handleAddOnClick = (e: Event) => {
196
+ e.preventDefault()
197
+ e.stopPropagation()
198
+ setShowDropdown(true)
199
+ }
200
+
201
+ addOnCard.addEventListener('click', handleAddOnClick)
202
+ addOnCard.style.cursor = 'pointer'
203
+
204
+ return () => {
205
+ addOnCard.removeEventListener('click', handleAddOnClick)
206
+ }
207
+ }
208
+ }, [uniqueId, disabled, setShowDropdown])
209
+
190
210
  // Input dropdown scrolling
191
211
  const scrollDropdownToSelected = (dropdownRef: React.RefObject<HTMLDivElement>) => {
192
212
  if (dropdownRef.current) {
@@ -369,6 +389,10 @@ const TimePicker = (props: TimePickerProps): JSX.Element => {
369
389
  }
370
390
  }
371
391
 
392
+ const handleHourFocus = (e: React.FocusEvent<HTMLInputElement>) => {
393
+ e.target.select()
394
+ }
395
+
372
396
  const handleHourBlur = () => {
373
397
  const result = normalizeHourOnBlur(hourInputValue, hour, timeFormat)
374
398
  setHour(result.hour)
@@ -393,6 +417,10 @@ const TimePicker = (props: TimePickerProps): JSX.Element => {
393
417
  }
394
418
  }
395
419
 
420
+ const handleMinuteFocus = (e: React.FocusEvent<HTMLInputElement>) => {
421
+ e.target.select()
422
+ }
423
+
396
424
  const handleMinuteBlur = () => {
397
425
  const result = normalizeMinuteOnBlur(minuteInputValue, minute)
398
426
  setMinute(result.minute)
@@ -482,6 +510,30 @@ const TimePicker = (props: TimePickerProps): JSX.Element => {
482
510
  e.preventDefault()
483
511
  setShowHourDropdown(false)
484
512
  closeDropdown()
513
+ } else if (e.key === 'ArrowDown') {
514
+ // ArrowDown increases value (like scrolling down a list)
515
+ e.preventDefault()
516
+ const { maxHour, minHour } = getHourConstraints(timeFormat)
517
+ const newHour = hour >= maxHour ? minHour : hour + 1
518
+ setHour(newHour)
519
+ setHourInputValue(timeFormat === '24hour' ? newHour.toString().padStart(2, '0') : newHour.toString())
520
+ setHasSelectedTime(true)
521
+ const timeString = get24HourTime(newHour, minute, meridiem, timeFormat)
522
+ if (onChange) {
523
+ onChange(timeString)
524
+ }
525
+ } else if (e.key === 'ArrowUp') {
526
+ // ArrowUp decreases value (like scrolling up a list)
527
+ e.preventDefault()
528
+ const { maxHour, minHour } = getHourConstraints(timeFormat)
529
+ const newHour = hour <= minHour ? maxHour : hour - 1
530
+ setHour(newHour)
531
+ setHourInputValue(timeFormat === '24hour' ? newHour.toString().padStart(2, '0') : newHour.toString())
532
+ setHasSelectedTime(true)
533
+ const timeString = get24HourTime(newHour, minute, meridiem, timeFormat)
534
+ if (onChange) {
535
+ onChange(timeString)
536
+ }
485
537
  }
486
538
  }
487
539
 
@@ -513,6 +565,28 @@ const TimePicker = (props: TimePickerProps): JSX.Element => {
513
565
  e.preventDefault()
514
566
  setShowMinuteDropdown(false)
515
567
  closeDropdown()
568
+ } else if (e.key === 'ArrowDown') {
569
+ // ArrowDown increases value (like scrolling down a list)
570
+ e.preventDefault()
571
+ const newMinute = minute >= 59 ? 0 : minute + 1
572
+ setMinute(newMinute)
573
+ setMinuteInputValue(newMinute.toString().padStart(2, '0'))
574
+ setHasSelectedTime(true)
575
+ const timeString = get24HourTime(hour, newMinute, meridiem, timeFormat)
576
+ if (onChange) {
577
+ onChange(timeString)
578
+ }
579
+ } else if (e.key === 'ArrowUp') {
580
+ // ArrowUp decreases value (like scrolling up a list)
581
+ e.preventDefault()
582
+ const newMinute = minute <= 0 ? 59 : minute - 1
583
+ setMinute(newMinute)
584
+ setMinuteInputValue(newMinute.toString().padStart(2, '0'))
585
+ setHasSelectedTime(true)
586
+ const timeString = get24HourTime(hour, newMinute, meridiem, timeFormat)
587
+ if (onChange) {
588
+ onChange(timeString)
589
+ }
516
590
  }
517
591
  }
518
592
 
@@ -625,12 +699,22 @@ const TimePicker = (props: TimePickerProps): JSX.Element => {
625
699
  >
626
700
  {label && (
627
701
  <label htmlFor={`${uniqueId}-input`}>
628
- <Caption
629
- className="pb_time_picker_kit_label"
630
- marginBottom="xs"
631
- size="md"
632
- text={label}
633
- />
702
+ {requiredIndicator ? (
703
+ <Caption
704
+ className="pb_time_picker_kit_label"
705
+ marginBottom="xs"
706
+ size="md"
707
+ >
708
+ {label} <span style={{ color: `${colors.error}` }}>{'*'}</span>
709
+ </Caption>
710
+ ) : (
711
+ <Caption
712
+ className="pb_time_picker_kit_label"
713
+ marginBottom="xs"
714
+ size="md"
715
+ text={label}
716
+ />
717
+ )}
634
718
  </label>
635
719
  )}
636
720
  <div className="time_picker_wrapper">
@@ -689,6 +773,7 @@ const TimePicker = (props: TimePickerProps): JSX.Element => {
689
773
  onBlur={handleHourBlur}
690
774
  onChange={handleHourChange}
691
775
  onClick={() => { setShowHourDropdown(!showHourDropdown); setShowMinuteDropdown(false) }}
776
+ onFocus={handleHourFocus}
692
777
  onKeyDown={handleHourKeyDown}
693
778
  pattern="[0-9]*"
694
779
  ref={hourInputRef}
@@ -734,6 +819,7 @@ const TimePicker = (props: TimePickerProps): JSX.Element => {
734
819
  onBlur={handleMinuteBlur}
735
820
  onChange={handleMinuteChange}
736
821
  onClick={() => { setShowMinuteDropdown(!showMinuteDropdown); setShowHourDropdown(false) }}
822
+ onFocus={handleMinuteFocus}
737
823
  onKeyDown={handleMinuteKeyDown}
738
824
  pattern="[0-9]*"
739
825
  ref={minuteInputRef}
@@ -1,6 +1,7 @@
1
1
  import React, { useState } from 'react'
2
2
  import TimePicker from '../../pb_time_picker/_time_picker'
3
3
  import Body from '../../pb_body/_body'
4
+ import Flex from '../../pb_flex/_flex'
4
5
 
5
6
  const TimePickerOnHandler = (props) => {
6
7
  const [selectedTime, setSelectedTime] = useState('')
@@ -17,7 +18,9 @@ const TimePickerOnHandler = (props) => {
17
18
  return (
18
19
  <div>
19
20
  {(selectedTime || closedTime) && (
20
- <div style={{ marginBottom: '16px' }}>
21
+ <Flex marginBottom="sm"
22
+ orientation="column"
23
+ >
21
24
  {selectedTime && (
22
25
  <Body
23
26
  text={`onChange: ${selectedTime}`}
@@ -29,7 +32,7 @@ const TimePickerOnHandler = (props) => {
29
32
  text={`onClose: ${closedTime}`}
30
33
  />
31
34
  )}
32
- </div>
35
+ </Flex>
33
36
  )}
34
37
  <TimePicker
35
38
  id="time-picker-on-handler"
@@ -0,0 +1,6 @@
1
+ <%= pb_rails("time_picker", props: {
2
+ id: "time-picker-required-indicator",
3
+ label: "Select Time",
4
+ required_indicator: true,
5
+ }) %>
6
+
@@ -0,0 +1,16 @@
1
+ import React from 'react'
2
+ import TimePicker from '../_time_picker'
3
+
4
+ const TimePickerRequiredIndicator = (props) => (
5
+ <div>
6
+ <TimePicker
7
+ id="time-picker-required-indicator"
8
+ label="Select Time"
9
+ requiredIndicator
10
+ {...props}
11
+ />
12
+ </div>
13
+ )
14
+
15
+ export default TimePickerRequiredIndicator
16
+
@@ -0,0 +1,3 @@
1
+ The `requiredIndicator`/`required_indicator` prop displays a red asterisk (*) next to the label, visually indicating that the field is required. This is purely visual and does not enforce validation.
2
+
3
+ You can use `requiredIndicator`/`required_indicator` with any validation approach: HTML5 validation via the `required` prop, client-side validation, or backend validation. For this reason, it works independently and doesn't need to be paired with the `required` prop.
@@ -9,6 +9,7 @@ examples:
9
9
  - time_picker_min_max_time: Min & Max Time
10
10
  - time_picker_error: Error
11
11
  - time_picker_disabled: Disabled
12
+ - time_picker_required_indicator: Required Indicator
12
13
  - time_picker_input_options: Input Options
13
14
 
14
15
 
@@ -21,4 +22,5 @@ examples:
21
22
  - time_picker_min_max_time: Min & Max Time
22
23
  - time_picker_error: Error
23
24
  - time_picker_disabled: Disabled
25
+ - time_picker_required_indicator: Required Indicator
24
26
  - time_picker_on_handler: onChange & onClose Handlers
@@ -7,3 +7,4 @@ export { default as TimePickerOnHandler } from './_time_picker_on_handler.jsx'
7
7
  export { default as TimePickerMinMaxTime } from './_time_picker_min_max_time.jsx'
8
8
  export { default as TimePickerError } from './_time_picker_error.jsx'
9
9
  export { default as TimePickerDisabled } from './_time_picker_disabled.jsx'
10
+ export { default as TimePickerRequiredIndicator } from './_time_picker_required_indicator.jsx'
@@ -16,6 +16,8 @@ module Playbook
16
16
  prop :name, type: Playbook::Props::String
17
17
  prop :required, type: Playbook::Props::Boolean,
18
18
  default: false
19
+ prop :required_indicator, type: Playbook::Props::Boolean,
20
+ default: false
19
21
  prop :show_timezone, type: Playbook::Props::Boolean,
20
22
  default: false
21
23
  prop :time_format, type: Playbook::Props::Enum,
@@ -68,6 +70,7 @@ module Playbook
68
70
  minTime: min_time,
69
71
  name: name,
70
72
  required: required,
73
+ requiredIndicator: required_indicator,
71
74
  showTimezone: show_timezone,
72
75
  timeFormat: time_format,
73
76
  validationMessage: validation_message,
@@ -1,5 +1,5 @@
1
1
  import React from 'react'
2
- import { render, screen, fireEvent } from '../utilities/test-utils'
2
+ import { render, screen, fireEvent, within } from '../utilities/test-utils'
3
3
 
4
4
  import TimePicker from './_time_picker'
5
5
 
@@ -111,4 +111,50 @@ describe('TimePicker', () => {
111
111
  expect(screen.getByText('AM')).toBeInTheDocument()
112
112
  expect(screen.getByText('PM')).toBeInTheDocument()
113
113
  })
114
+
115
+ test('renders required indicator asterisk when requiredIndicator is true', () => {
116
+ render(
117
+ <TimePicker
118
+ data={{ testid: 'required-indicator-picker' }}
119
+ label="Select Time"
120
+ requiredIndicator
121
+ />
122
+ )
123
+ const kit = screen.getByTestId('required-indicator-picker')
124
+ const label = within(kit).getByText(/Select Time/)
125
+
126
+ expect(label).toBeInTheDocument()
127
+ expect(kit).toHaveTextContent('*')
128
+ })
129
+
130
+ test('requiredIndicator works independently of required prop', () => {
131
+ render(
132
+ <TimePicker
133
+ data={{ testid: 'indicator-without-required' }}
134
+ label="Select Time"
135
+ requiredIndicator
136
+ />
137
+ )
138
+ const kit = screen.getByTestId('indicator-without-required')
139
+
140
+ expect(kit).toHaveTextContent('*')
141
+ const input = screen.getByPlaceholderText('Select Time')
142
+ expect(input).not.toHaveAttribute('required')
143
+ })
144
+
145
+ test('requiredIndicator and required can be used together', () => {
146
+ render(
147
+ <TimePicker
148
+ data={{ testid: 'both-props-picker' }}
149
+ label="Select Time"
150
+ required
151
+ requiredIndicator
152
+ />
153
+ )
154
+ const kit = screen.getByTestId('both-props-picker')
155
+ const input = screen.getByPlaceholderText('Select Time')
156
+
157
+ expect(kit).toHaveTextContent('*')
158
+ expect(input).toHaveAttribute('required')
159
+ })
114
160
  })