playbook_ui 16.1.0.pre.alpha.play264213818 → 16.1.0.pre.alpha.play274314102

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 (159) 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_card/docs/_card_light.html.erb +3 -35
  9. data/app/pb_kits/playbook/pb_checkbox/_checkbox.scss +1 -1
  10. data/app/pb_kits/playbook/pb_checkbox/_checkbox.tsx +17 -0
  11. data/app/pb_kits/playbook/pb_checkbox/checkbox.html.erb +10 -1
  12. data/app/pb_kits/playbook/pb_checkbox/checkbox.rb +2 -0
  13. data/app/pb_kits/playbook/pb_checkbox/docs/_checkbox_required_indicator.html.erb +6 -0
  14. data/app/pb_kits/playbook/pb_checkbox/docs/_checkbox_required_indicator.jsx +17 -0
  15. data/app/pb_kits/playbook/pb_checkbox/docs/_checkbox_required_indicator.md +3 -0
  16. data/app/pb_kits/playbook/pb_checkbox/docs/example.yml +2 -0
  17. data/app/pb_kits/playbook/pb_checkbox/docs/index.js +1 -0
  18. data/app/pb_kits/playbook/pb_date_picker/_date_picker.tsx +14 -5
  19. data/app/pb_kits/playbook/pb_date_picker/docs/_date_picker_default.md +1 -0
  20. data/app/pb_kits/playbook/pb_dialog/_dialog.scss +8 -6
  21. data/app/pb_kits/playbook/pb_dropdown/_dropdown.scss +6 -0
  22. data/app/pb_kits/playbook/pb_dropdown/_dropdown.tsx +83 -13
  23. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_blank_selection_rails.md +3 -0
  24. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_blank_selection_react.md +3 -0
  25. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_clearable.html.erb +52 -0
  26. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_clearable.jsx +72 -0
  27. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_clearable.md +5 -0
  28. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_constrain_height.jsx +33 -0
  29. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_constrain_height_rails.html.erb +20 -0
  30. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_constrain_height_rails.md +8 -0
  31. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_constrain_height_react.md +8 -0
  32. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_label.html.erb +6 -3
  33. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_label.jsx +1 -0
  34. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_label.md +3 -1
  35. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_placeholder.html.erb +9 -0
  36. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_placeholder.jsx +33 -0
  37. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_placeholder.md +3 -0
  38. data/app/pb_kits/playbook/pb_dropdown/docs/example.yml +6 -0
  39. data/app/pb_kits/playbook/pb_dropdown/docs/index.js +4 -1
  40. data/app/pb_kits/playbook/pb_dropdown/dropdown.html.erb +11 -5
  41. data/app/pb_kits/playbook/pb_dropdown/dropdown.rb +15 -0
  42. data/app/pb_kits/playbook/pb_dropdown/dropdown.test.jsx +94 -0
  43. data/app/pb_kits/playbook/pb_dropdown/dropdown_container.rb +5 -1
  44. data/app/pb_kits/playbook/pb_dropdown/dropdown_trigger.html.erb +7 -2
  45. data/app/pb_kits/playbook/pb_dropdown/dropdown_trigger.rb +4 -0
  46. data/app/pb_kits/playbook/pb_dropdown/index.js +184 -77
  47. data/app/pb_kits/playbook/pb_dropdown/subcomponents/DropdownContainer.tsx +3 -0
  48. data/app/pb_kits/playbook/pb_dropdown/subcomponents/DropdownTrigger.tsx +18 -1
  49. data/app/pb_kits/playbook/pb_dropdown/utilities/clickOutsideHelper.tsx +6 -0
  50. data/app/pb_kits/playbook/pb_filter/Filter/SortMenu.tsx +1 -1
  51. data/app/pb_kits/playbook/pb_filter/docs/_filter_default.html.erb +2 -2
  52. data/app/pb_kits/playbook/pb_filter/docs/_filter_default.jsx +16 -9
  53. data/app/pb_kits/playbook/pb_filter/filter.rb +2 -2
  54. data/app/pb_kits/playbook/pb_form/docs/_form_with_required_indicator.html.erb +6 -2
  55. data/app/pb_kits/playbook/pb_form/pb_form_validation.js +9 -2
  56. data/app/pb_kits/playbook/pb_form_pill/docs/_form_pill_truncated_text.html.erb +5 -5
  57. data/app/pb_kits/playbook/pb_form_pill/docs/_form_pill_truncated_text.jsx +4 -4
  58. data/app/pb_kits/playbook/pb_form_pill/form_pill.rb +4 -0
  59. data/app/pb_kits/playbook/pb_multi_level_select/_multi_level_select.scss +7 -0
  60. data/app/pb_kits/playbook/pb_multi_level_select/_multi_level_select.tsx +638 -549
  61. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_label.html.erb +3 -3
  62. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_label.jsx +4 -7
  63. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_label.md +3 -0
  64. data/app/pb_kits/playbook/pb_multi_level_select/multi_level_select.test.jsx +4 -4
  65. data/app/pb_kits/playbook/pb_passphrase/_passphrase.tsx +40 -7
  66. data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_meter_settings.jsx +1 -0
  67. data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_required_indicator.html.erb +7 -0
  68. data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_required_indicator.jsx +24 -0
  69. data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_required_indicator.md +3 -0
  70. data/app/pb_kits/playbook/pb_passphrase/docs/example.yml +2 -0
  71. data/app/pb_kits/playbook/pb_passphrase/docs/index.js +1 -0
  72. data/app/pb_kits/playbook/pb_passphrase/passphrase.rb +2 -0
  73. data/app/pb_kits/playbook/pb_passphrase/passphrase.test.jsx +30 -1
  74. data/app/pb_kits/playbook/pb_phone_number_input/_phone_number_input.tsx +3 -0
  75. data/app/pb_kits/playbook/pb_phone_number_input/docs/_phone_number_input_required_indicator.html.erb +5 -0
  76. data/app/pb_kits/playbook/pb_phone_number_input/docs/_phone_number_input_required_indicator.jsx +14 -0
  77. data/app/pb_kits/playbook/pb_phone_number_input/docs/_phone_number_input_required_indicator.md +3 -0
  78. data/app/pb_kits/playbook/pb_phone_number_input/docs/example.yml +2 -0
  79. data/app/pb_kits/playbook/pb_phone_number_input/docs/index.js +1 -0
  80. data/app/pb_kits/playbook/pb_phone_number_input/phone_number_input.rb +3 -0
  81. data/app/pb_kits/playbook/pb_phone_number_input/phone_number_input.test.js +34 -3
  82. data/app/pb_kits/playbook/pb_rich_text_editor/_rich_text_editor.tsx +71 -34
  83. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_advanced_label.jsx +44 -0
  84. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_advanced_label.md +1 -0
  85. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_advanced_required_indicator.jsx +1 -0
  86. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_attributes.jsx +4 -0
  87. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_default.jsx +4 -0
  88. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_focus.jsx +5 -0
  89. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_inline.jsx +4 -0
  90. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_label.jsx +33 -0
  91. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_label.md +1 -0
  92. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_preview.jsx +4 -0
  93. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_required_indicator.jsx +5 -0
  94. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_simple.jsx +4 -0
  95. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_sticky.jsx +4 -0
  96. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_templates.jsx +4 -0
  97. data/app/pb_kits/playbook/pb_rich_text_editor/docs/example.yml +23 -20
  98. data/app/pb_kits/playbook/pb_rich_text_editor/docs/index.js +2 -1
  99. data/app/pb_kits/playbook/pb_table/index.ts +29 -27
  100. data/app/pb_kits/playbook/pb_text_input/text_input.html.erb +10 -10
  101. data/app/pb_kits/playbook/pb_textarea/_textarea.tsx +10 -0
  102. data/app/pb_kits/playbook/pb_textarea/docs/_textarea_default.html.erb +3 -3
  103. data/app/pb_kits/playbook/pb_textarea/docs/_textarea_default.jsx +3 -0
  104. data/app/pb_kits/playbook/pb_textarea/docs/_textarea_default.md +1 -0
  105. data/app/pb_kits/playbook/pb_textarea/textarea.html.erb +25 -9
  106. data/app/pb_kits/playbook/pb_textarea/textarea.rb +7 -1
  107. data/app/pb_kits/playbook/pb_time_picker/_time_picker.tsx +97 -11
  108. data/app/pb_kits/playbook/pb_time_picker/docs/_time_picker_on_handler.jsx +5 -2
  109. data/app/pb_kits/playbook/pb_time_picker/docs/_time_picker_required_indicator.html.erb +6 -0
  110. data/app/pb_kits/playbook/pb_time_picker/docs/_time_picker_required_indicator.jsx +16 -0
  111. data/app/pb_kits/playbook/pb_time_picker/docs/_time_picker_required_indicator.md +3 -0
  112. data/app/pb_kits/playbook/pb_time_picker/docs/example.yml +2 -0
  113. data/app/pb_kits/playbook/pb_time_picker/docs/index.js +1 -0
  114. data/app/pb_kits/playbook/pb_time_picker/time_picker.rb +3 -0
  115. data/app/pb_kits/playbook/pb_time_picker/time_picker.test.jsx +47 -1
  116. data/app/pb_kits/playbook/pb_typeahead/_typeahead.test.jsx +24 -1
  117. data/app/pb_kits/playbook/pb_typeahead/_typeahead.tsx +412 -324
  118. data/app/pb_kits/playbook/pb_typeahead/components/Control.tsx +2 -0
  119. data/app/pb_kits/playbook/pb_typeahead/components/MultiValue.tsx +4 -1
  120. data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_required_indicator.html.erb +16 -0
  121. data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_required_indicator.jsx +23 -0
  122. data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_required_indicator.md +3 -0
  123. data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_truncated_text.html.erb +1 -1
  124. data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_truncated_text.jsx +1 -1
  125. data/app/pb_kits/playbook/pb_typeahead/docs/example.yml +2 -0
  126. data/app/pb_kits/playbook/pb_typeahead/docs/index.js +22 -21
  127. data/app/pb_kits/playbook/pb_typeahead/typeahead.html.erb +3 -2
  128. data/app/pb_kits/playbook/pb_typeahead/typeahead.rb +7 -1
  129. data/dist/chunks/{_pb_line_graph-BgKF_zz1.js → _pb_line_graph-DuJNCf7N.js} +1 -1
  130. data/dist/chunks/_typeahead-Cx2lp7TD.js +1 -0
  131. data/dist/chunks/{globalProps-BhVYCqRf.js → globalProps-Bc-FVsRt.js} +1 -1
  132. data/dist/chunks/lib-BwX82vim.js +29 -0
  133. data/dist/chunks/vendor.js +3 -3
  134. data/dist/menu.yml +2 -2
  135. data/dist/playbook-rails-react-bindings.js +1 -1
  136. data/dist/playbook-rails.js +1 -1
  137. data/dist/playbook.css +1 -1
  138. data/lib/playbook/forms/builder/form_field_builder.rb +1 -1
  139. data/lib/playbook/forms/builder/phone_number_field.rb +9 -0
  140. data/lib/playbook/forms/builder/typeahead_field.rb +15 -1
  141. data/lib/playbook/forms/builder.rb +2 -2
  142. data/lib/playbook/truncate.rb +1 -1
  143. data/lib/playbook/version.rb +1 -1
  144. metadata +42 -19
  145. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_attributes.html.erb +0 -5
  146. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_default.html.erb +0 -1
  147. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_focus.html.erb +0 -3
  148. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_inline.html.erb +0 -6
  149. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_preview.html.erb +0 -35
  150. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_required_indicator.html.erb +0 -10
  151. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_simple.html.erb +0 -1
  152. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_sticky.html.erb +0 -1
  153. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_templates.html.erb +0 -115
  154. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_toolbar_bottom.html.erb +0 -4
  155. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_toolbar_bottom.jsx +0 -14
  156. data/app/pb_kits/playbook/pb_rich_text_editor/rich_text_editor.html.erb +0 -5
  157. data/app/pb_kits/playbook/pb_rich_text_editor/rich_text_editor.rb +0 -63
  158. data/dist/chunks/_typeahead-B9a6ZsEP.js +0 -1
  159. data/dist/chunks/lib-DD34ZrWL.js +0 -29
@@ -65,7 +65,7 @@
65
65
  }] %>
66
66
 
67
67
  <%= pb_rails("multi_level_select", props: {
68
- id: "multi-level-select-label-rails",
69
- label: "Select a department",
70
- tree_data: treeData
68
+ id: "select_a_department",
69
+ label: "Select a Department",
70
+ tree_data: treeData
71
71
  }) %>
@@ -73,14 +73,11 @@ const MultiLevelSelectDefault = (props) => {
73
73
  return (
74
74
  <div>
75
75
  <MultiLevelSelect
76
- id='multiselect-label'
76
+ id="select_a_department"
77
77
  label="Select a Department"
78
78
  onSelect={(selectedNodes) =>
79
- console.log(
80
- "Selected Items",
81
- selectedNodes
82
- )
83
- }
79
+ console.log("Selected Items", selectedNodes)
80
+ }
84
81
  treeData={treeData}
85
82
  {...props}
86
83
  />
@@ -88,4 +85,4 @@ const MultiLevelSelectDefault = (props) => {
88
85
  )
89
86
  };
90
87
 
91
- export default MultiLevelSelectDefault;
88
+ export default MultiLevelSelectDefault;
@@ -0,0 +1,3 @@
1
+ The MultiLevelSelect component optionally accepts a `label` prop to produce a label above the input.
2
+
3
+ Add an `id` to wire the label to the input so that clicking the label will move focus directly to the input, and open the drop-down.
@@ -192,7 +192,7 @@ describe('MultiLevelSelect multi variant', () => {
192
192
  />
193
193
  )
194
194
  const kit = screen.getByTestId(testId)
195
- const input = kit.querySelector('#multiselect_input')
195
+ const input = kit.querySelector('#multi-disabled-test_input')
196
196
  fireEvent.click(input)
197
197
 
198
198
  const disabledCheckbox = kit.querySelector('input[type="checkbox"][disabled]')
@@ -227,7 +227,7 @@ describe('MultiLevelSelect single variant', () => {
227
227
  />
228
228
  )
229
229
  const kit = screen.getByTestId(testId)
230
- const input = kit.querySelector('#multiselect_input')
230
+ const input = kit.querySelector('#single-disabled-test_input')
231
231
  fireEvent.click(input)
232
232
 
233
233
  const disabledRadio = kit.querySelector('input[type="radio"][disabled]')
@@ -246,7 +246,7 @@ describe('MultiLevelSelect single variant', () => {
246
246
  />
247
247
  )
248
248
  const kit = screen.getByTestId(testId)
249
- const input = kit.querySelector('#multiselect_input')
249
+ const input = kit.querySelector('#single-disabled-click-test_input')
250
250
  fireEvent.click(input)
251
251
 
252
252
  const disabledRadio = kit.querySelector('input[type="radio"][disabled]')
@@ -267,7 +267,7 @@ describe('MultiLevelSelect single variant', () => {
267
267
  />
268
268
  )
269
269
  const kit = screen.getByTestId(testId)
270
- const input = kit.querySelector('#multiselect_input')
270
+ const input = kit.querySelector('#single-enabled-click-test_input')
271
271
  fireEvent.click(input)
272
272
 
273
273
  const enabledRadio = kit.querySelector('input[type="radio"]:not([disabled])')
@@ -7,6 +7,7 @@ import { globalProps } from "../utilities/globalProps"
7
7
  import Body from '../pb_body/_body'
8
8
  import Caption from '../pb_caption/_caption'
9
9
  import CircleIconButton from '../pb_circle_icon_button/_circle_icon_button'
10
+ import colors from '../tokens/exports/_colors.module.scss'
10
11
  import Flex from '../pb_flex/_flex'
11
12
  import Icon from '../pb_icon/_icon'
12
13
  import PbReactPopover from '../pb_popover/_popover'
@@ -25,6 +26,7 @@ type PassphraseProps = {
25
26
  inputProps?: GenericObject,
26
27
  label?: string,
27
28
  onChange: (inputValue: string) => void,
29
+ requiredIndicator?: boolean,
28
30
  showTipsBelow?: "always" | "xs" | "sm" | "md" | "lg" | "xl",
29
31
  tips?: Array<string>,
30
32
  uncontrolled?: boolean,
@@ -43,6 +45,7 @@ const Passphrase = (props: PassphraseProps): React.ReactElement => {
43
45
  inputProps = {},
44
46
  label = confirmation ? "Confirm Passphrase" : "Passphrase",
45
47
  onChange = () => undefined,
48
+ requiredIndicator = false,
46
49
  showTipsBelow = "always",
47
50
  tips = [],
48
51
  uncontrolled = false,
@@ -99,6 +102,7 @@ const Passphrase = (props: PassphraseProps): React.ReactElement => {
99
102
 
100
103
  const shieldIcon = getAllIcons()["shieldCheck"]
101
104
  const eyeIcon = getAllIcons()["eye"]
105
+ const hasLabel = label && label !== ""
102
106
 
103
107
  return (
104
108
  <div
@@ -109,11 +113,22 @@ const Passphrase = (props: PassphraseProps): React.ReactElement => {
109
113
  id={id}
110
114
  >
111
115
  <label>
112
- <Flex align="baseline">
113
- <Caption
114
- className="passphrase-label"
115
- text={label}
116
- />
116
+ <Flex
117
+ align="baseline"
118
+ {...(hasLabel ? { marginBottom: "xs" } : {})}
119
+ >
120
+ {hasLabel && (requiredIndicator ? (
121
+ <Caption
122
+ className="passphrase-label"
123
+ >
124
+ {label} <span style={{ color: `${colors.error}` }}>*</span>
125
+ </Caption>
126
+ ) : (
127
+ <Caption
128
+ className="passphrase-label"
129
+ text={label}
130
+ />
131
+ ))}
117
132
  {tips.length > 0 && !confirmation &&
118
133
  <PbReactPopover
119
134
  className="passphrase-tips"
@@ -163,22 +178,40 @@ const Passphrase = (props: PassphraseProps): React.ReactElement => {
163
178
  {...inputProps}
164
179
  />
165
180
  <span
181
+ aria-label={
182
+ showPassphrase
183
+ ? "Passphrase currently visible. Click icon to hide password"
184
+ : "Passphrase currently hidden. Click icon to reveal password"
185
+ }
186
+ aria-pressed={showPassphrase}
166
187
  className="show-passphrase-icon"
167
188
  onClick={toggleShowPassphrase}
189
+ onKeyDown={(e) => {
190
+ if (e.key === "Enter" || e.key === " ") {
191
+ e.preventDefault()
192
+ toggleShowPassphrase(e as any)
193
+ }
194
+ }}
195
+ role="button"
196
+ tabIndex={0}
168
197
  >
169
198
  <Body
170
199
  className={showPassphrase ? "hide-icon" : ""}
171
200
  color="light"
172
201
  dark={dark}
173
202
  >
174
- <Icon icon="eye-slash" />
203
+ <Icon
204
+ aria={{ label: "eye icon" }}
205
+ icon="eye-slash"
206
+ />
175
207
  </Body>
176
208
  <Body
177
209
  className={showPassphrase ? "" : "hide-icon"}
178
210
  color="light"
179
211
  dark={dark}
180
212
  >
181
- <Icon
213
+ <Icon
214
+ aria={{ label: "eye icon" }}
182
215
  className="svg-inline--fa"
183
216
  customIcon={eyeIcon.icon as unknown as { [key: string]: SVGElement }}
184
217
  />
@@ -120,6 +120,7 @@ const PassphraseMeterSettings = (props) => {
120
120
  "These examples will all share the same input value. Type in any of the inputs to see how the strength meter changes in response to different settings."
121
121
  }
122
122
  </Body>
123
+ <br/>
123
124
  <Passphrase
124
125
  label={"Type your passphrase"}
125
126
  onChange={handleChange}
@@ -0,0 +1,7 @@
1
+ <%= pb_rails("passphrase", props: {
2
+ id: "passphrase_required_indicator",
3
+ label: "Passphrase",
4
+ placeholder: "Enter passphrase",
5
+ required_indicator: true,
6
+ value: "passphrase",
7
+ }) %>
@@ -0,0 +1,24 @@
1
+ import React, { useState } from 'react'
2
+
3
+ import Passphrase from '../_passphrase'
4
+
5
+ const PassphraseRequiredIndicator = (props) => {
6
+ const [passphrase, setPassphrase] = useState('')
7
+ const handleOnChangePassphrase = (e) => {
8
+ setPassphrase(e.target ? e.target.value : e)
9
+ }
10
+
11
+ return (
12
+ <Passphrase
13
+ id="passphrase_required_indicator"
14
+ label="Passphrase"
15
+ name="passphrase"
16
+ onChange={handleOnChangePassphrase}
17
+ requiredIndicator
18
+ value={passphrase}
19
+ {...props}
20
+ />
21
+ )
22
+ }
23
+
24
+ export default PassphraseRequiredIndicator
@@ -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
  - passphrase_strength_change: Strength Change
10
10
  - passphrase_common: Common Passphrases
11
11
  - passphrase_breached: Breached Passphrases
12
+ - passphrase_required_indicator: Required Indicator
12
13
 
13
14
  react:
14
15
  - passphrase_default: Default
@@ -19,3 +20,4 @@ examples:
19
20
  - passphrase_strength_change: Strength Change
20
21
  - passphrase_common: Common Passphrases
21
22
  - passphrase_breached: Breached Passphrases
23
+ - passphrase_required_indicator: Required Indicator
@@ -6,3 +6,4 @@ export { default as PassphraseTips } from './_passphrase_tips'
6
6
  export { default as PassphraseStrengthChange } from './_passphrase_strength_change'
7
7
  export { default as PassphraseCommon } from './_passphrase_common'
8
8
  export { default as PassphraseBreached } from './_passphrase_breached'
9
+ export { default as PassphraseRequiredIndicator } from './_passphrase_required_indicator.jsx'
@@ -10,6 +10,7 @@ module Playbook
10
10
  values: %w[always xs sm md lg xl],
11
11
  default: "always"
12
12
  prop :tips, type: Playbook::Props::Array, default: []
13
+ prop :required_indicator, type: Playbook::Props::Boolean, default: false
13
14
  prop :value, type: Playbook::Props::String
14
15
 
15
16
  def classname
@@ -23,6 +24,7 @@ module Playbook
23
24
  confirmation: confirmation,
24
25
  inputProps: input_props,
25
26
  label: label,
27
+ requiredIndicator: required_indicator,
26
28
  showTipsBelow: show_tips_below,
27
29
  tips: tips,
28
30
  uncontrolled: true,
@@ -1,5 +1,5 @@
1
1
  import React from 'react'
2
- import { render, screen } from '../utilities/test-utils'
2
+ import { render, screen, within } from '../utilities/test-utils'
3
3
  import { Passphrase } from 'playbook-ui'
4
4
 
5
5
  const testId = 'text-input1',
@@ -86,3 +86,32 @@ test('popover target does not show when tips are not given', () => {
86
86
  const kit = screen.getByTestId(testId)
87
87
  expect(kit.querySelector('[class^=pb_popover_reference_wrapper]')).toBeNull()
88
88
  })
89
+
90
+ test('renders required indicator asterisk when requiredIndicator is true', () => {
91
+ render(
92
+ <Passphrase
93
+ data={{ testid: testId }}
94
+ label="Passphrase"
95
+ requiredIndicator
96
+ />
97
+ )
98
+
99
+ const kit = screen.getByTestId(testId)
100
+ const label = within(kit).getByText(/Passphrase/)
101
+ expect(label).toBeInTheDocument()
102
+ expect(kit).toHaveTextContent('*')
103
+ })
104
+
105
+ test('does not render required indicator asterisk when requiredIndicator is false', () => {
106
+ render(
107
+ <Passphrase
108
+ data={{ testid: testId }}
109
+ label="Passphrase"
110
+ />
111
+ )
112
+
113
+ const kit = screen.getByTestId(testId)
114
+ const label = within(kit).getByText(/Passphrase/)
115
+ expect(label).toBeInTheDocument()
116
+ expect(kit).not.toHaveTextContent('*')
117
+ })
@@ -36,6 +36,7 @@ type PhoneNumberInputProps = {
36
36
  excludeCountries: string[],
37
37
  preferredCountries?: string[],
38
38
  required?: boolean,
39
+ requiredIndicator?: boolean,
39
40
  value?: string,
40
41
  formatAsYouType?: boolean,
41
42
  strictMode?: boolean,
@@ -91,6 +92,7 @@ const PhoneNumberInput = (props: PhoneNumberInputProps, ref?: React.Ref<unknown>
91
92
  onlyCountries = [],
92
93
  excludeCountries = [],
93
94
  required = false,
95
+ requiredIndicator = false,
94
96
  preferredCountries = [],
95
97
  value = "",
96
98
  formatAsYouType = false,
@@ -533,6 +535,7 @@ const PhoneNumberInput = (props: PhoneNumberInputProps, ref?: React.Ref<unknown>
533
535
  validateErrors()
534
536
  },
535
537
  onChange: formatAsYouType ? undefined : handleOnChange,
538
+ requiredIndicator,
536
539
  value: inputValue
537
540
  }
538
541
 
@@ -0,0 +1,5 @@
1
+ <%= pb_rails("phone_number_input", props: {
2
+ id: "phone_number_input_required_indicator",
3
+ label: "Required Phone Number",
4
+ required_indicator: true,
5
+ }) %>
@@ -0,0 +1,14 @@
1
+ import React from 'react'
2
+ import PhoneNumberInput from '../../pb_phone_number_input/_phone_number_input'
3
+
4
+ const PhoneNumberInputRequiredIndicator = (props) => (
5
+ <>
6
+ <PhoneNumberInput
7
+ id='phone_number_input_required_indicator'
8
+ label='Phone Number'
9
+ requiredIndicator
10
+ {...props} />
11
+ </>
12
+ )
13
+
14
+ export default PhoneNumberInputRequiredIndicator
@@ -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.
@@ -12,6 +12,7 @@ examples:
12
12
  - phone_number_input_format: Format as You Type
13
13
  - phone_number_input_strict_mode: Strict Mode
14
14
  - phone_number_input_country_search: Country Search
15
+ - phone_number_input_required_indicator: Required Indicator
15
16
 
16
17
  rails:
17
18
  - phone_number_input_default: Default
@@ -24,3 +25,4 @@ examples:
24
25
  - phone_number_input_strict_mode: Strict Mode
25
26
  - phone_number_input_hidden_inputs: Hidden Inputs
26
27
  - phone_number_input_country_search: Country Search
28
+ - phone_number_input_required_indicator: Required Indicator
@@ -9,3 +9,4 @@ export { default as PhoneNumberInputAccessInputElement } from './_phone_number_i
9
9
  export { default as PhoneNumberInputFormat } from './_phone_number_input_format'
10
10
  export { default as PhoneNumberInputStrictMode } from './_phone_number_input_strict_mode'
11
11
  export { default as PhoneNumberInputCountrySearch } from './_phone_number_input_country_search'
12
+ export { default as PhoneNumberInputRequiredIndicator } from './_phone_number_input_required_indicator.jsx'
@@ -7,6 +7,8 @@ module Playbook
7
7
  default: false
8
8
  prop :required, type: Playbook::Props::Boolean,
9
9
  default: false
10
+ prop :required_indicator, type: Playbook::Props::Boolean,
11
+ default: false
10
12
  prop :initial_country, type: Playbook::Props::String,
11
13
  default: ""
12
14
  prop :label, type: Playbook::Props::String,
@@ -52,6 +54,7 @@ module Playbook
52
54
  excludeCountries: exclude_countries,
53
55
  preferredCountries: preferred_countries,
54
56
  required: required,
57
+ requiredIndicator: required_indicator,
55
58
  value: value,
56
59
  countrySearch: country_search,
57
60
  }
@@ -1,5 +1,5 @@
1
1
  import React from "react";
2
- import { render, screen, act } from "../utilities/test-utils";
2
+ import { render, screen, act, within } from "../utilities/test-utils";
3
3
  import PhoneNumberInput from "./_phone_number_input";
4
4
 
5
5
  const testId = "phoneNumberInput";
@@ -129,7 +129,7 @@ test("should format phone number as '555-555-5555' with formatAsYouType and 'us'
129
129
  };
130
130
 
131
131
  render(<PhoneNumberInput {...props} />);
132
-
132
+
133
133
  const input = screen.getByRole("textbox");
134
134
 
135
135
  act(() => {
@@ -154,7 +154,38 @@ test("should pass countrySearch prop to component", () => {
154
154
  };
155
155
 
156
156
  render(<PhoneNumberInput {...props} />);
157
-
157
+
158
158
  const wrapper = screen.getByTestId('phone-input-with-search');
159
159
  expect(wrapper).toBeInTheDocument();
160
160
  });
161
+
162
+ test("renders required indicator asterisk when requiredIndicator is true", () => {
163
+ const props = {
164
+ data: { testid: testId },
165
+ id: testId,
166
+ label: "Required Phone Number",
167
+ requiredIndicator: true,
168
+ };
169
+
170
+ render(<PhoneNumberInput {...props} />);
171
+
172
+ const kit = screen.getByTestId(testId);
173
+ const label = within(kit).getByText(/Required Phone Number/);
174
+ expect(label).toBeInTheDocument();
175
+ expect(kit).toHaveTextContent("*");
176
+ });
177
+
178
+ test("does not render required indicator asterisk when requiredIndicator is false", () => {
179
+ const props = {
180
+ data: { testid: testId },
181
+ id: testId,
182
+ label: "Phone Number",
183
+ };
184
+
185
+ render(<PhoneNumberInput {...props} />);
186
+
187
+ const kit = screen.getByTestId(testId);
188
+ const label = within(kit).getByText(/Phone Number/);
189
+ expect(label).toBeInTheDocument();
190
+ expect(kit).not.toHaveTextContent("*");
191
+ });
@@ -1,6 +1,6 @@
1
1
  import React, { useEffect, useState, useRef } from 'react'
2
2
  import classnames from 'classnames'
3
- import { TrixEditor } from 'react-trix'
3
+ // The user must import and pass TrixEditor as a prop if using the default editor
4
4
 
5
5
  import inlineFocus from './inlineFocus'
6
6
  import useFocus from './useFocus'
@@ -9,14 +9,6 @@ import colors from '../tokens/exports/_colors.module.scss'
9
9
  import { globalProps, GlobalProps } from '../utilities/globalProps'
10
10
  import { buildAriaProps, buildDataProps, noop, buildHtmlProps } from '../utilities/props'
11
11
 
12
- import Trix from 'trix'
13
- import './_dedupe_trix_toolbar'
14
-
15
- Trix.config.textAttributes.inlineCode = {
16
- tagName: 'code',
17
- inheritable: true,
18
- }
19
-
20
12
  import EditorToolbar from './TipTap/Toolbar'
21
13
 
22
14
  type Editor = {
@@ -55,6 +47,7 @@ type RichTextEditorProps = {
55
47
  template: string,
56
48
  value?: string,
57
49
  maxWidth?: string
50
+ TrixEditor?: React.ComponentType<any>,
58
51
  } & GlobalProps
59
52
 
60
53
  const RichTextEditor = (props: RichTextEditorProps): React.ReactElement => {
@@ -84,6 +77,7 @@ const RichTextEditor = (props: RichTextEditorProps): React.ReactElement => {
84
77
  maxWidth = "md",
85
78
  requiredIndicator = false,
86
79
  label,
80
+ TrixEditor,
87
81
  } = props
88
82
 
89
83
  const ariaProps = buildAriaProps(aria),
@@ -94,25 +88,51 @@ const RichTextEditor = (props: RichTextEditorProps): React.ReactElement => {
94
88
 
95
89
  const htmlProps = buildHtmlProps(htmlOptions)
96
90
 
91
+ const fieldId = id ? (id as string) : null
92
+ const labelElementId = fieldId ? `${fieldId}-label` : null
93
+
97
94
  const handleOnEditorReady = (editorInstance: Editor) => {
98
95
  setEditor(editorInstance)
96
+
99
97
  setTimeout(() => {
100
- const oldId = editorInstance.element.getAttribute('input')
101
- if (oldId) {
102
- const hiddenInput = document.getElementById(oldId)
103
- if (hiddenInput) {
104
- const newId = (inputOptions.id as string) || oldId
105
- hiddenInput.id = newId
106
- editorInstance.element.setAttribute('input', newId)
107
-
108
- if (inputOptions.name) {
109
- hiddenInput.setAttribute('name', inputOptions.name as string)
110
- }
111
- }
98
+ const oldId = editorInstance.element?.getAttribute("input")
99
+ if (!oldId) return
100
+
101
+ const hiddenInput = document.getElementById(oldId) as HTMLElement | null
102
+ if (!hiddenInput) return
103
+
104
+ const hiddenInputId = (inputOptions.id as string) || oldId
105
+
106
+ if (hiddenInputId !== oldId) {
107
+ hiddenInput.id = hiddenInputId
108
+ editorInstance.element?.setAttribute("input", hiddenInputId)
112
109
  }
110
+
111
+ if (inputOptions.name) {
112
+ hiddenInput.setAttribute("name", inputOptions.name as string)
113
+ }
114
+
115
+ const editorDomId = (id as string) || `${hiddenInputId}_trix`
116
+ const trixLabelId = ((id as string) || hiddenInputId) + "-label"
117
+
118
+ if (label) {
119
+ editorInstance.element?.setAttribute("aria-labelledby", trixLabelId)
120
+ }
121
+ editorInstance.element!.id = editorDomId
113
122
  })
114
123
  }
115
124
 
125
+ useEffect(() => {
126
+ if (!advancedEditor || !fieldId || !labelElementId) return
127
+
128
+ const dom = advancedEditor.view?.dom as HTMLElement | undefined
129
+ if (!dom) return
130
+
131
+ dom.setAttribute("aria-labelledby", labelElementId)
132
+ dom.setAttribute("role", "textbox")
133
+ dom.setAttribute("aria-multiline", "true")
134
+ }, [advancedEditor, fieldId, labelElementId])
135
+
116
136
  // DOM manipulation must wait for editor to be ready
117
137
  if (editor && editor.element) {
118
138
  const toolbarElement = editor.element.parentElement.querySelector('trix-toolbar') as HTMLElement,
@@ -214,6 +234,8 @@ const RichTextEditor = (props: RichTextEditorProps): React.ReactElement => {
214
234
  // Determine if toolbar should be shown
215
235
  const shouldShowToolbar = focus && advancedEditor ? showToolbarOnFocus : advancedEditorToolbar
216
236
 
237
+ const labelFor = advancedEditor ? fieldId : (id ? id : (inputOptions.id ? `${inputOptions.id}_trix` : undefined))
238
+
217
239
  return (
218
240
  <div
219
241
  {...ariaProps}
@@ -223,7 +245,14 @@ const RichTextEditor = (props: RichTextEditorProps): React.ReactElement => {
223
245
  ref={focus ? containerRef : undefined}
224
246
  >
225
247
  {label && (
226
- <label htmlFor={id}>
248
+ <label
249
+ {...(labelFor ? { htmlFor: labelFor, id: labelElementId } : {})}
250
+ onMouseDown={(e) => {
251
+ if (!advancedEditor || !fieldId) return
252
+ e.preventDefault()
253
+ advancedEditor.commands.focus()
254
+ }}
255
+ >
227
256
  {
228
257
  requiredIndicator ? (
229
258
  <Caption className="pb_text_input_kit_label"
@@ -246,9 +275,9 @@ const RichTextEditor = (props: RichTextEditorProps): React.ReactElement => {
246
275
  advancedEditor ? (
247
276
  <div
248
277
  className={classnames(
249
- "pb_rich_text_editor_advanced_container",
250
- { [`input_height_${inputHeight}`]: !!inputHeight,[`input_min_height_${inputMinHeight}`]: !!inputMinHeight ,["toolbar-active"]: shouldShowToolbar }
251
- )}
278
+ "pb_rich_text_editor_advanced_container",
279
+ { [`input_height_${inputHeight}`]: !!inputHeight,[`input_min_height_${inputMinHeight}`]: !!inputMinHeight ,["toolbar-active"]: shouldShowToolbar }
280
+ )}
252
281
  >
253
282
  {shouldShowToolbar && (
254
283
  <EditorToolbar editor={advancedEditor}
@@ -260,15 +289,23 @@ const RichTextEditor = (props: RichTextEditorProps): React.ReactElement => {
260
289
  { children }
261
290
  </div>
262
291
  ) : (
263
- <TrixEditor
264
- className=""
265
- fileParamName={name}
266
- mergeTags={[]}
267
- onChange={onChange}
268
- onEditorReady={handleOnEditorReady}
269
- placeholder={placeholder}
270
- value={value}
271
- />
292
+ TrixEditor ? (
293
+ <TrixEditor
294
+ className=""
295
+ fileParamName={name}
296
+ mergeTags={[]}
297
+ onChange={onChange}
298
+ onEditorReady={handleOnEditorReady}
299
+ placeholder={placeholder}
300
+ value={value}
301
+ />
302
+ ) : (
303
+ <div style={{ color: 'red', padding: '1em', border: '1px solid #f00', background: '#fff0f0' }}>
304
+ <strong>Trix Editor is not available.</strong><br />
305
+ Please import <code>TrixEditor</code> from <code>react-trix</code> and pass it as a prop to <code>RichTextEditor</code>.<br />
306
+ <pre>{`import { TrixEditor } from 'react-trix';\n<RichTextEditor TrixEditor={TrixEditor} ... />`}</pre>
307
+ </div>
308
+ )
272
309
  )
273
310
  }
274
311
  </div>
@@ -0,0 +1,44 @@
1
+ import React from 'react'
2
+ import RichTextEditor from '../../pb_rich_text_editor/_rich_text_editor'
3
+ import { useEditor, EditorContent } from "@tiptap/react"
4
+ import StarterKit from "@tiptap/starter-kit"
5
+ import Link from '@tiptap/extension-link'
6
+
7
+
8
+ const RichTextEditorAdvancedLabel = (props) => {
9
+
10
+ const editor = useEditor({
11
+ extensions: [StarterKit, Link],
12
+ content: "Add your text here. You can format your text, add links, quotes, and bullets.",
13
+ })
14
+
15
+ const editorNoLabel = useEditor({
16
+ extensions: [StarterKit, Link],
17
+ content: "Add your text here. You can format your text, add links, quotes, and bullets.",
18
+ })
19
+
20
+ if (!editor || !editorNoLabel) return null
21
+
22
+ return (
23
+ <div>
24
+ <RichTextEditor
25
+ advancedEditor={editor}
26
+ id={"advanced-example"}
27
+ label="Advanced Example Label"
28
+ {...props}
29
+ >
30
+ <EditorContent editor={editor}/>
31
+ </RichTextEditor>
32
+ <br/>
33
+ <RichTextEditor
34
+ advancedEditor={editorNoLabel}
35
+ label="Advanced Example Label No ID"
36
+ {...props}
37
+ >
38
+ <EditorContent editor={editorNoLabel}/>
39
+ </RichTextEditor>
40
+ </div>
41
+ )
42
+ }
43
+
44
+ export default RichTextEditorAdvancedLabel