playbook_ui 16.1.0.pre.alpha.play276813969 → 16.1.0.pre.alpha.testingrailsfix14159

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 (71) 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_form/docs/_form_with_required_indicator.html.erb +6 -3
  20. data/app/pb_kits/playbook/pb_form/pb_form_validation.js +9 -2
  21. data/app/pb_kits/playbook/pb_passphrase/_passphrase.tsx +20 -2
  22. data/app/pb_kits/playbook/pb_rich_text_editor/_rich_text_editor.tsx +51 -16
  23. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_advanced_label.jsx +44 -0
  24. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_advanced_label.md +1 -0
  25. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_advanced_required_indicator.jsx +1 -0
  26. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_label.jsx +28 -0
  27. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_label.md +1 -0
  28. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_required_indicator.jsx +1 -0
  29. data/app/pb_kits/playbook/pb_rich_text_editor/docs/example.yml +2 -0
  30. data/app/pb_kits/playbook/pb_rich_text_editor/docs/index.js +2 -0
  31. data/app/pb_kits/playbook/pb_table/index.ts +29 -27
  32. data/app/pb_kits/playbook/pb_text_input/text_input.html.erb +10 -10
  33. data/app/pb_kits/playbook/pb_textarea/_textarea.tsx +10 -0
  34. data/app/pb_kits/playbook/pb_textarea/docs/_textarea_default.html.erb +3 -3
  35. data/app/pb_kits/playbook/pb_textarea/docs/_textarea_default.jsx +3 -0
  36. data/app/pb_kits/playbook/pb_textarea/docs/_textarea_default.md +1 -0
  37. data/app/pb_kits/playbook/pb_textarea/textarea.html.erb +25 -9
  38. data/app/pb_kits/playbook/pb_textarea/textarea.rb +7 -1
  39. data/app/pb_kits/playbook/pb_time_picker/_time_picker.tsx +97 -11
  40. data/app/pb_kits/playbook/pb_time_picker/docs/_time_picker_on_handler.jsx +5 -2
  41. data/app/pb_kits/playbook/pb_time_picker/docs/_time_picker_required_indicator.html.erb +6 -0
  42. data/app/pb_kits/playbook/pb_time_picker/docs/_time_picker_required_indicator.jsx +16 -0
  43. data/app/pb_kits/playbook/pb_time_picker/docs/_time_picker_required_indicator.md +3 -0
  44. data/app/pb_kits/playbook/pb_time_picker/docs/example.yml +2 -0
  45. data/app/pb_kits/playbook/pb_time_picker/docs/index.js +1 -0
  46. data/app/pb_kits/playbook/pb_time_picker/time_picker.rb +3 -0
  47. data/app/pb_kits/playbook/pb_time_picker/time_picker.test.jsx +47 -1
  48. data/app/pb_kits/playbook/pb_typeahead/_typeahead.tsx +410 -323
  49. data/app/pb_kits/playbook/pb_typeahead/components/Control.tsx +2 -0
  50. data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_required_indicator.html.erb +16 -0
  51. data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_required_indicator.jsx +23 -0
  52. data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_required_indicator.md +3 -0
  53. data/app/pb_kits/playbook/pb_typeahead/docs/example.yml +2 -0
  54. data/app/pb_kits/playbook/pb_typeahead/docs/index.js +22 -21
  55. data/app/pb_kits/playbook/pb_typeahead/typeahead.html.erb +3 -2
  56. data/app/pb_kits/playbook/pb_typeahead/typeahead.rb +3 -1
  57. data/dist/chunks/{_pb_line_graph-BgKF_zz1.js → _pb_line_graph-DuJNCf7N.js} +1 -1
  58. data/dist/chunks/_typeahead-BKSzddAX.js +1 -0
  59. data/dist/chunks/{globalProps-BhVYCqRf.js → globalProps-Bc-FVsRt.js} +1 -1
  60. data/dist/chunks/lib-BwX82vim.js +29 -0
  61. data/dist/chunks/vendor.js +3 -3
  62. data/dist/menu.yml +2 -2
  63. data/dist/playbook-rails-react-bindings.js +1 -1
  64. data/dist/playbook-rails.js +1 -1
  65. data/lib/playbook/forms/builder/form_field_builder.rb +1 -1
  66. data/lib/playbook/forms/builder/typeahead_field.rb +15 -1
  67. data/lib/playbook/forms/builder.rb +2 -2
  68. data/lib/playbook/version.rb +1 -1
  69. metadata +23 -6
  70. data/dist/chunks/_typeahead-C4YsbA48.js +0 -1
  71. data/dist/chunks/lib-DD34ZrWL.js +0 -29
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 387f03d299bddebe2e4ac7e9800fddf28bb00cdeceb0ffcd9a5c4ea1ed211d20
4
- data.tar.gz: 03b9b1cbcb66afd3afd4d28eb8c0226dc4d7a2ddc883dadb0844bc427f857831
3
+ metadata.gz: d322d1484a0695b5c047cf03aa19f8af74cf5ad1728dc20a327eb2447c986d61
4
+ data.tar.gz: 042e05bf1e98557de7c10f400b547d52d806788e4e14e40fdf374934eed0a0d9
5
5
  SHA512:
6
- metadata.gz: e4f7c1a63a205b06dadc63869cbfb9f761d88775d9d625efe37bd896d1cbd5269107b71fc0c24714bb781c2b314e949aa5a37bcab80eed8689fe0d90b72796f9
7
- data.tar.gz: 7933fda91c7fe525372cb559bcc56147fe86bc877cac737603ee0694b62f34fdef93cd2e40b88860de6a51f0c389e21ac2ae6d858cd1cb14531a31083e78bd7c
6
+ metadata.gz: a29ba74bbb1ceefdc7817720135660996607782e6ecbe661b112bd23d7d576dd8526de402247e20551c94acba07ee146c1522f58b3165368926a49af0c00c9e0
7
+ data.tar.gz: c10ac2431f78b6b2d88a0ae45f7543d501341449d1d79d62a2321a93e2594c03aa36ef678f4f2377667801d261fde114228b8ad09c06b3cd71b61206ff6b5f3b
@@ -66,8 +66,18 @@ const TableCellRenderer = ({
66
66
  // Find the "owning" colDefinition by accessor. Needed for multi column logic
67
67
  const colDef = findColumnDefByAccessor(columnDefinitions ?? [], column.id)
68
68
  const cellAlignment = colDef?.columnStyling?.cellAlignment ?? "right"
69
- const cellFontColor = colDef?.columnStyling?.fontColor
70
- const cellBackgroundColor = colDef?.columnStyling?.cellBackgroundColor
69
+
70
+ // Support function-based styling for conditional rendering
71
+ const cellFontColorValue = colDef?.columnStyling?.fontColor
72
+ const cellFontColor = typeof cellFontColorValue === 'function'
73
+ ? cellFontColorValue(row)
74
+ : cellFontColorValue
75
+
76
+ const cellBackgroundColorValue = colDef?.columnStyling?.cellBackgroundColor
77
+ const cellBackgroundColor = typeof cellBackgroundColorValue === 'function'
78
+ ? cellBackgroundColorValue(row)
79
+ : cellBackgroundColorValue
80
+
71
81
  const paddingValue = colDef?.columnStyling?.cellPadding ?? customRowStyle?.cellPadding
72
82
  const paddingClass = paddingValue ? `p_${paddingValue}` : undefined
73
83
 
@@ -920,6 +920,39 @@ test("columnStyling.backgroundColor works as excpected", () => {
920
920
  expect(firstEnrollmentCell).toHaveStyle({ backgroundColor: colors.error_subtle });
921
921
  });
922
922
 
923
+ test("columnStyling.cellBackgroundColor works as expected with function", () => {
924
+ const styledColumnDefs = [
925
+ {
926
+ accessor: "year",
927
+ label: "Year",
928
+ cellAccessors: ["quarter", "month", "day"],
929
+ },
930
+ {
931
+ accessor: "newEnrollments",
932
+ label: "New Enrollments",
933
+ columnStyling:{
934
+ cellBackgroundColor: (row) => row.original.newEnrollments > 15 ? colors.success_subtle : colors.error_subtle,
935
+ fontColor: (row) => row.original.newEnrollments > 15 ? colors.success : colors.error,
936
+ },
937
+ },
938
+ {
939
+ accessor: "scheduledMeetings",
940
+ label: "Scheduled Meetings",
941
+ },
942
+ ];
943
+
944
+ render(
945
+ <AdvancedTable
946
+ columnDefinitions={styledColumnDefs}
947
+ data={{ testid: testId }}
948
+ tableData={MOCK_DATA}
949
+ />
950
+ );
951
+
952
+ const firstEnrollmentCell = screen.getAllByText("20")[0].closest("td");
953
+ expect(firstEnrollmentCell).toHaveStyle({ backgroundColor: colors.success_subtle, color: colors.success });
954
+ });
955
+
923
956
  test("columnStyling.headerBackgroundColor works as excpected", () => {
924
957
  const styledColumnDefs = [
925
958
  {
@@ -0,0 +1,71 @@
1
+ import React from "react"
2
+ import AdvancedTable from '../_advanced_table'
3
+ import colors from '../../tokens/exports/_colors.module.scss'
4
+ import MOCK_DATA from "./advanced_table_mock_data.json"
5
+ import Flex from '../../pb_flex/_flex'
6
+ import Title from '../../pb_title/_title'
7
+ import Body from '../../pb_body/_body'
8
+
9
+
10
+ const AdvancedTableColumnStylingBackgroundCustom = (props) => {
11
+ const columnDefinitions = [
12
+ {
13
+ accessor: "year",
14
+ label: "Year",
15
+ cellAccessors: ["quarter", "month", "day"],
16
+ customRenderer: (row, value) => (
17
+ <Flex flexDirection="column">
18
+ <Title size={4}
19
+ text={value}
20
+ />
21
+ <Body text="lorem ipsum" />
22
+ <Body text="lorem ipsum" />
23
+ </Flex>
24
+ ),
25
+ },
26
+ {
27
+ accessor: "newEnrollments",
28
+ label: "New Enrollments",
29
+ columnStyling:{
30
+ cellBackgroundColor: (row) => row.original.newEnrollments > 15 ? colors.success_subtle : colors.error_subtle,
31
+ fontColor: (row) => row.original.newEnrollments > 15 ? colors.success : colors.error,
32
+ },
33
+ },
34
+ {
35
+ accessor: "scheduledMeetings",
36
+ label: "Scheduled Meetings",
37
+ columnStyling:{
38
+ cellBackgroundColor: (row) => row.original.scheduledMeetings >= 15 ? colors.info_subtle : colors.warning_subtle
39
+ },
40
+ },
41
+ {
42
+ accessor: "attendanceRate",
43
+ label: "Attendance Rate",
44
+ columnStyling:{cellBackgroundColor: colors.info, headerBackgroundColor: colors.info, fontColor: colors.white, headerFontColor: colors.white},
45
+ },
46
+ {
47
+ accessor: "completedClasses",
48
+ label: "Completed Classes",
49
+ },
50
+ {
51
+ accessor: "classCompletionRate",
52
+ label: "Class Completion Rate",
53
+ },
54
+ {
55
+ accessor: "graduatedStudents",
56
+ label: "Graduated Students",
57
+ },
58
+ ]
59
+
60
+ return (
61
+ <div>
62
+ <AdvancedTable
63
+ columnDefinitions={columnDefinitions}
64
+ tableData={MOCK_DATA}
65
+ {...props}
66
+ />
67
+ </div>
68
+ )
69
+ }
70
+
71
+ export default AdvancedTableColumnStylingBackgroundCustom
@@ -0,0 +1,4 @@
1
+ `cellBackgroundColor` and `fontColor` can also accept functions for conditional styling based on row data. The function receives the row object and returns a color value.
2
+
3
+ See the code snippet below for more details.
4
+
@@ -78,6 +78,7 @@ examples:
78
78
  - advanced_table_column_styling: Column Styling
79
79
  - advanced_table_column_styling_column_headers: Column Styling with Multiple Headers
80
80
  - advanced_table_column_styling_background: Column Styling Background Color
81
+ - advanced_table_column_styling_background_custom: Column Styling Background Color (Custom)
81
82
  - advanced_table_column_styling_background_multi: Column Styling Background Color with Multiple Headers
82
83
  - advanced_table_padding_control: Padding Control using Column Styling
83
84
  - advanced_table_column_border_color: Column Group Border Color
@@ -47,4 +47,5 @@ export { default as AdvancedTableSortPerColumnForMultiColumn } from './_advanced
47
47
  export { default as AdvancedTablePaddingControl } from './_advanced_table_padding_control.jsx'
48
48
  export { default as AdvancedTablePaddingControlPerRow } from './_advanced_table_padding_control_per_row.jsx'
49
49
  export { default as AdvancedTableColumnStylingBackground } from './_advanced_table_column_styling_background.jsx'
50
- export { default as AdvancedTableColumnStylingBackgroundMulti } from './_advanced_table_column_styling_background_multi.jsx'
50
+ export { default as AdvancedTableColumnStylingBackgroundMulti } from './_advanced_table_column_styling_background_multi.jsx'
51
+ export { default as AdvancedTableColumnStylingBackgroundCustom } from './_advanced_table_column_styling_background_custom.jsx'
@@ -168,4 +168,4 @@ $transition: $transition_cubic;
168
168
  border-color: $error;
169
169
  }
170
170
  }
171
- }
171
+ }
@@ -4,6 +4,8 @@ import Icon from '../pb_icon/_icon'
4
4
  import { buildAriaProps, buildCss, buildDataProps, buildHtmlProps } from '../utilities/props'
5
5
  import classnames from 'classnames'
6
6
  import { globalProps, GlobalProps } from '../utilities/globalProps'
7
+ import colors from '../tokens/exports/_colors.module.scss'
8
+ import spacing from '../tokens/exports/_spacing.module.scss'
7
9
 
8
10
  type CheckboxProps = {
9
11
  aria?: {[key: string]: string},
@@ -19,6 +21,7 @@ type CheckboxProps = {
19
21
  indeterminate?: boolean,
20
22
  name?: string,
21
23
  onChange?: (event: React.FormEvent<HTMLInputElement>) => void,
24
+ requiredIndicator?: boolean,
22
25
  tabIndex?: number,
23
26
  text?: string,
24
27
  value?: string,
@@ -39,6 +42,7 @@ const Checkbox = forwardRef<HTMLInputElement, CheckboxProps>((props, ref) => {
39
42
  indeterminate = false,
40
43
  name = '',
41
44
  onChange = () => { void 0 },
45
+ requiredIndicator = false,
42
46
  tabIndex,
43
47
  text = '',
44
48
  value = '',
@@ -124,7 +128,20 @@ const Checkbox = forwardRef<HTMLInputElement, CheckboxProps>((props, ref) => {
124
128
  variant={null}
125
129
  >
126
130
  {text}
131
+ {requiredIndicator && (
132
+ <span
133
+ aria-hidden="true"
134
+ className="pb_required_indicator"
135
+ style={{
136
+ color: colors.error,
137
+ marginLeft: spacing.space_xs,
138
+ }}
139
+ >
140
+ {'*'}
141
+ </span>
142
+ )}
127
143
  </Body>
144
+
128
145
  </label>
129
146
  )
130
147
  })
@@ -5,6 +5,15 @@
5
5
  <%= pb_rails("icon", props: { icon: "minus", classname: "indeterminate_icon hidden", fixed_width: true}) %>
6
6
  </span>
7
7
  <span class="pb_checkbox_label">
8
- <%= pb_rails("body", props: { status: object.checkbox_label_status, text: object.text, dark: object.dark, margin_right: object.form_spacing ? "xs" : "" }) %>
8
+ <%= pb_rails("body", props: { status: object.checkbox_label_status, dark: object.dark, margin_right: object.form_spacing ? "xs" : "" }) do %>
9
+ <%= object.text%>
10
+ <% if object.required_indicator %>
11
+ <span
12
+ class="pb_checkbox_required_indicator"
13
+ aria-hidden="true"
14
+ style="color: #DA0014;"
15
+ >*</span>
16
+ <% end %>
17
+ <% end %>
9
18
  </span>
10
19
  <% end %>
@@ -23,6 +23,8 @@ module Playbook
23
23
  prop :hidden_input, type: Playbook::Props::Boolean,
24
24
  default: false
25
25
  prop :hidden_value
26
+ prop :required_indicator, type: Playbook::Props::Boolean,
27
+ default: false
26
28
 
27
29
  def classname
28
30
  generate_classname("pb_checkbox_kit", checked_class) + error_class
@@ -0,0 +1,6 @@
1
+ <%= pb_rails("checkbox" , props: {
2
+ required_indicator: true,
3
+ text: "Checkbox Label",
4
+ value: "checkbox-value",
5
+ name: "checkbox-name"
6
+ }) %>
@@ -0,0 +1,17 @@
1
+ import React from 'react'
2
+ import Checkbox from '../_checkbox'
3
+
4
+ const CheckboxRequiredIndicator = () => {
5
+ return (
6
+ <div>
7
+ <Checkbox
8
+ name="checkbox-name"
9
+ requiredIndicator
10
+ text="Checkbox label"
11
+ value="check-box value"
12
+ />
13
+ </div>
14
+ )
15
+ }
16
+
17
+ export default CheckboxRequiredIndicator
@@ -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.
@@ -8,6 +8,7 @@ examples:
8
8
  - checkbox_indeterminate: Indeterminate Checkbox
9
9
  - checkbox_disabled: Disabled Checkbox
10
10
  - checkbox_form: Form and Hidden Input
11
+ # - checkbox_required_indicator: Required Indicator
11
12
 
12
13
  react:
13
14
  - checkbox_default: Default
@@ -17,6 +18,7 @@ examples:
17
18
  - checkbox_error: Default w/ Error
18
19
  - checkbox_indeterminate: Indeterminate Checkbox
19
20
  - checkbox_disabled: Disabled Checkbox
21
+ # - checkbox_required_indicator: Required Indicator
20
22
 
21
23
  swift:
22
24
  - checkbox_default_swift: Default
@@ -5,3 +5,4 @@ export { default as CheckboxError } from './_checkbox_error.jsx'
5
5
  export { default as CheckboxChecked } from './_checkbox_checked.jsx'
6
6
  export { default as CheckboxIndeterminate } from './_checkbox_indeterminate.jsx'
7
7
  export { default as CheckboxDisabled } from './_checkbox_disabled.jsx'
8
+ export { default as CheckboxRequiredIndicator } from './_checkbox_required_indicator.jsx'
@@ -40,7 +40,7 @@ type DatePickerProps = {
40
40
  maxDate: string,
41
41
  minDate: string,
42
42
  name: string,
43
- pickerId?: string,
43
+ pickerId: string,
44
44
  placeholder?: string,
45
45
  positionElement?: HTMLElement | null,
46
46
  scrollContainer?: string,
@@ -196,6 +196,8 @@ const DatePicker = (props: DatePickerProps): React.ReactElement => {
196
196
 
197
197
  const angleDown = getAllIcons()["angleDown"].icon as unknown as { [key: string]: SVGElement }
198
198
 
199
+ const errorId = error ? `${pickerId}-error` : undefined
200
+
199
201
  return (
200
202
  <div
201
203
  {...ariaProps}
@@ -211,14 +213,18 @@ const DatePicker = (props: DatePickerProps): React.ReactElement => {
211
213
  >
212
214
 
213
215
  {!hideLabel && (
214
- <Caption
215
- className="pb_date_picker_kit_label"
216
- text={label}
217
- />
216
+ <label htmlFor={pickerId}>
217
+ <Caption
218
+ className="pb_date_picker_kit_label"
219
+ text={label}
220
+ />
221
+ </label>
218
222
  )}
219
223
  <>
220
224
  <div className="date_picker_input_wrapper">
221
225
  <input
226
+ aria-describedby={errorId}
227
+ aria-invalid={!!error}
222
228
  autoComplete="off"
223
229
  className="date_picker_input"
224
230
  disabled={disableInput}
@@ -232,6 +238,9 @@ const DatePicker = (props: DatePickerProps): React.ReactElement => {
232
238
 
233
239
  {error &&
234
240
  <Body
241
+ aria={{ atomic: "true", live: "polite" }}
242
+ htmlOptions={{ role: "alert" }}
243
+ id={errorId}
235
244
  status="negative"
236
245
  text={error}
237
246
  variant={null}
@@ -0,0 +1 @@
1
+ `pickerId`/`picker_id` is a **required prop** to instantiate the Date Picker. The presence of `pickerId`/`picker_id` in your Date Picker also associates the label with the input, providing the ability to focus the Date Picker by clicking the label.
@@ -1,14 +1,17 @@
1
1
  <%= pb_form_with(scope: :example, url: "", method: :get, validate: true) do |form| %>
2
+ <%= form.typeahead :example_typeahead_field, props: { label: true, required: true, required_indicator: true } %>
2
3
  <%= form.text_field :example_text_field, props: { label: true, required: true, required_indicator: true } %>
3
4
  <%= form.text_field :example_text_field_2, props: { label: "Text Field Custom Label", required: true, required_indicator: true } %>
4
- <%= form.text_area :example_text_area, props: { label: true, required: true, required_indicator: true } %>
5
- <%= form.text_area :example_text_area_2, props: { label: "Textarea Custom Label", required: true, required_indicator: true } %>
5
+ <%= form.phone_number_field :example_phone_number_field, props: { label: true, required: true, required_indicator: true } %>
6
6
  <%= form.email_field :example_email_field, props: { label: true, required: true, required_indicator: true } %>
7
7
  <%= form.number_field :example_number_field, props: { label: true, required: true, required_indicator: true } %>
8
8
  <%= form.search_field :example_search_field, props: { label: true, required: true, required_indicator: true } %>
9
9
  <%= form.password_field :example_password_field, props: { label: true, required: true, required_indicator: true } %>
10
10
  <%= form.url_field :example_url_field, props: { label: true, required: true, required_indicator: true } %>
11
- <%= form.phone_number_field :example_phone_number_field, props: { label: true, required: true, required_indicator: true } %>
11
+ <%= form.text_area :example_text_area, props: { label: true, required: true, required_indicator: true } %>
12
+ <%= form.text_area :example_text_area_2, props: { label: "Textarea Custom Label", required: true, required_indicator: true } %>
13
+ <%# <%= form.check_box :example_checkbox, props: { label: true, text: "Checkbox Label", required: true, required_indicator: true } %>
14
+ <%= form.time_picker :example_time_picker_required_indicator, props: { label: true, required: true, required_indicator: true } %>
12
15
 
13
16
  <%= form.actions do |action| %>
14
17
  <%= action.submit %>
@@ -26,6 +26,10 @@ class PbFormValidation extends PbEnhancedElement {
26
26
  const isPhoneNumberInput = field.closest('.pb_phone_number_input')
27
27
  if (isPhoneNumberInput) return
28
28
 
29
+ // Skip TimePicker inputs - they handle their own validation
30
+ const isTimePickerInput = field.closest('.pb_time_picker')
31
+ if (isTimePickerInput) return
32
+
29
33
  FIELD_EVENTS.forEach((e) => {
30
34
  field.addEventListener(e, debounce((event) => {
31
35
  this.validateFormField(event)
@@ -67,13 +71,16 @@ class PbFormValidation extends PbEnhancedElement {
67
71
 
68
72
  // Check if this is a phone number input
69
73
  const isPhoneNumberInput = kitElement.classList.contains('pb_phone_number_input')
74
+
75
+ // Check if this is a TimePicker input
76
+ const isTimePickerInput = kitElement.classList.contains('pb_time_picker')
70
77
 
71
78
  // ensure clean error message state
72
79
  this.clearError(target)
73
80
  kitElement.classList.add('error')
74
81
 
75
- // Only add error message if it's NOT a phone number input
76
- if (!isPhoneNumberInput) {
82
+ // Only add error message if it's NOT a phone number input or TimePicker input
83
+ if (!isPhoneNumberInput && !isTimePickerInput) {
77
84
  // set the error message element
78
85
  const errorMessageContainer = this.errorMessageContainer
79
86
 
@@ -178,22 +178,40 @@ const Passphrase = (props: PassphraseProps): React.ReactElement => {
178
178
  {...inputProps}
179
179
  />
180
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}
181
187
  className="show-passphrase-icon"
182
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}
183
197
  >
184
198
  <Body
185
199
  className={showPassphrase ? "hide-icon" : ""}
186
200
  color="light"
187
201
  dark={dark}
188
202
  >
189
- <Icon icon="eye-slash" />
203
+ <Icon
204
+ aria={{ label: "eye icon" }}
205
+ icon="eye-slash"
206
+ />
190
207
  </Body>
191
208
  <Body
192
209
  className={showPassphrase ? "" : "hide-icon"}
193
210
  color="light"
194
211
  dark={dark}
195
212
  >
196
- <Icon
213
+ <Icon
214
+ aria={{ label: "eye icon" }}
197
215
  className="svg-inline--fa"
198
216
  customIcon={eyeIcon.icon as unknown as { [key: string]: SVGElement }}
199
217
  />
@@ -94,25 +94,51 @@ const RichTextEditor = (props: RichTextEditorProps): React.ReactElement => {
94
94
 
95
95
  const htmlProps = buildHtmlProps(htmlOptions)
96
96
 
97
+ const fieldId = id ? (id as string) : null
98
+ const labelElementId = fieldId ? `${fieldId}-label` : null
99
+
97
100
  const handleOnEditorReady = (editorInstance: Editor) => {
98
101
  setEditor(editorInstance)
102
+
99
103
  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
- }
104
+ const oldId = editorInstance.element?.getAttribute("input")
105
+ if (!oldId) return
106
+
107
+ const hiddenInput = document.getElementById(oldId) as HTMLElement | null
108
+ if (!hiddenInput) return
109
+
110
+ const hiddenInputId = (inputOptions.id as string) || oldId
111
+
112
+ if (hiddenInputId !== oldId) {
113
+ hiddenInput.id = hiddenInputId
114
+ editorInstance.element?.setAttribute("input", hiddenInputId)
115
+ }
116
+
117
+ if (inputOptions.name) {
118
+ hiddenInput.setAttribute("name", inputOptions.name as string)
112
119
  }
120
+
121
+ const editorDomId = (id as string) || `${hiddenInputId}_trix`
122
+ const trixLabelId = ((id as string) || hiddenInputId) + "-label"
123
+
124
+ if (label) {
125
+ editorInstance.element?.setAttribute("aria-labelledby", trixLabelId)
126
+ }
127
+ editorInstance.element!.id = editorDomId
113
128
  })
114
129
  }
115
130
 
131
+ useEffect(() => {
132
+ if (!advancedEditor || !fieldId || !labelElementId) return
133
+
134
+ const dom = advancedEditor.view?.dom as HTMLElement | undefined
135
+ if (!dom) return
136
+
137
+ dom.setAttribute("aria-labelledby", labelElementId)
138
+ dom.setAttribute("role", "textbox")
139
+ dom.setAttribute("aria-multiline", "true")
140
+ }, [advancedEditor, fieldId, labelElementId])
141
+
116
142
  // DOM manipulation must wait for editor to be ready
117
143
  if (editor && editor.element) {
118
144
  const toolbarElement = editor.element.parentElement.querySelector('trix-toolbar') as HTMLElement,
@@ -214,6 +240,8 @@ const RichTextEditor = (props: RichTextEditorProps): React.ReactElement => {
214
240
  // Determine if toolbar should be shown
215
241
  const shouldShowToolbar = focus && advancedEditor ? showToolbarOnFocus : advancedEditorToolbar
216
242
 
243
+ const labelFor = advancedEditor ? fieldId : (id ? id : (inputOptions.id ? `${inputOptions.id}_trix` : undefined))
244
+
217
245
  return (
218
246
  <div
219
247
  {...ariaProps}
@@ -223,7 +251,14 @@ const RichTextEditor = (props: RichTextEditorProps): React.ReactElement => {
223
251
  ref={focus ? containerRef : undefined}
224
252
  >
225
253
  {label && (
226
- <label htmlFor={id}>
254
+ <label
255
+ {...(labelFor ? { htmlFor: labelFor, id: labelElementId } : {})}
256
+ onMouseDown={(e) => {
257
+ if (!advancedEditor || !fieldId) return
258
+ e.preventDefault()
259
+ advancedEditor.commands.focus()
260
+ }}
261
+ >
227
262
  {
228
263
  requiredIndicator ? (
229
264
  <Caption className="pb_text_input_kit_label"
@@ -246,9 +281,9 @@ const RichTextEditor = (props: RichTextEditorProps): React.ReactElement => {
246
281
  advancedEditor ? (
247
282
  <div
248
283
  className={classnames(
249
- "pb_rich_text_editor_advanced_container",
250
- { [`input_height_${inputHeight}`]: !!inputHeight,[`input_min_height_${inputMinHeight}`]: !!inputMinHeight ,["toolbar-active"]: shouldShowToolbar }
251
- )}
284
+ "pb_rich_text_editor_advanced_container",
285
+ { [`input_height_${inputHeight}`]: !!inputHeight,[`input_min_height_${inputMinHeight}`]: !!inputMinHeight ,["toolbar-active"]: shouldShowToolbar }
286
+ )}
252
287
  >
253
288
  {shouldShowToolbar && (
254
289
  <EditorToolbar editor={advancedEditor}
@@ -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
@@ -0,0 +1 @@
1
+ The optional `label` prop adds a visible label to the advanced editor. Passing in the `id` prop associates the `label` with the editor for accessibility, enabling screen reader support and label-based focus behavior.
@@ -22,6 +22,7 @@ const RichTextEditorAdvancedRequiredIndicator = (props) => {
22
22
  <div>
23
23
  <RichTextEditor
24
24
  advancedEditor={editor}
25
+ id={"required-advanced"}
25
26
  label="Label"
26
27
  requiredIndicator
27
28
  {...props}
@@ -0,0 +1,28 @@
1
+ import React, { useState } from 'react'
2
+ import RichTextEditor from '../../pb_rich_text_editor/_rich_text_editor'
3
+
4
+ const RichTextEditorLabel = (props) => {
5
+ const [value, setValue] = useState(''),
6
+ handleOnChange = (html) => setValue(html)
7
+
8
+ return (
9
+ <div>
10
+ <RichTextEditor
11
+ id="example"
12
+ label="Example Label"
13
+ onChange={handleOnChange}
14
+ value={value}
15
+ {...props}
16
+ />
17
+ <br/>
18
+ <RichTextEditor
19
+ label="Example Label No ID"
20
+ onChange={handleOnChange}
21
+ value={value}
22
+ {...props}
23
+ />
24
+ </div>
25
+ )
26
+ }
27
+
28
+ export default RichTextEditorLabel
@@ -0,0 +1 @@
1
+ The optional `label` prop adds a visible label to the editor. Passing in the `id` prop associates the `label` with the editor for accessibility, enabling screen reader support and label-based focus behavior.
@@ -8,6 +8,7 @@ const RichTextEditorRequiredIndicator = (props) => {
8
8
  return (
9
9
  <div>
10
10
  <RichTextEditor
11
+ inputOptions = {{ id: "required" }}
11
12
  label="Label"
12
13
  onChange={handleOnChange}
13
14
  requiredIndicator