playbook_ui 16.2.0.pre.rc.1 → 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 (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_dropdown/_dropdown.tsx +46 -11
  18. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_label.html.erb +6 -3
  19. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_label.jsx +1 -0
  20. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_label.md +3 -1
  21. data/app/pb_kits/playbook/pb_dropdown/dropdown.html.erb +10 -4
  22. data/app/pb_kits/playbook/pb_dropdown/dropdown.rb +9 -0
  23. data/app/pb_kits/playbook/pb_dropdown/dropdown_trigger.html.erb +7 -2
  24. data/app/pb_kits/playbook/pb_dropdown/dropdown_trigger.rb +4 -0
  25. data/app/pb_kits/playbook/pb_dropdown/index.js +125 -73
  26. data/app/pb_kits/playbook/pb_dropdown/subcomponents/DropdownTrigger.tsx +16 -0
  27. data/app/pb_kits/playbook/pb_dropdown/utilities/clickOutsideHelper.tsx +6 -0
  28. data/app/pb_kits/playbook/pb_form/docs/_form_with_required_indicator.html.erb +5 -3
  29. data/app/pb_kits/playbook/pb_multi_level_select/_multi_level_select.scss +7 -0
  30. data/app/pb_kits/playbook/pb_multi_level_select/_multi_level_select.tsx +638 -549
  31. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_label.html.erb +3 -3
  32. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_label.jsx +4 -7
  33. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_label.md +3 -0
  34. data/app/pb_kits/playbook/pb_multi_level_select/multi_level_select.test.jsx +4 -4
  35. data/app/pb_kits/playbook/pb_passphrase/_passphrase.tsx +20 -2
  36. data/app/pb_kits/playbook/pb_rich_text_editor/_rich_text_editor.tsx +51 -16
  37. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_advanced_label.jsx +44 -0
  38. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_advanced_label.md +1 -0
  39. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_advanced_required_indicator.jsx +1 -0
  40. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_label.jsx +28 -0
  41. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_label.md +1 -0
  42. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_required_indicator.jsx +1 -0
  43. data/app/pb_kits/playbook/pb_rich_text_editor/docs/example.yml +2 -0
  44. data/app/pb_kits/playbook/pb_rich_text_editor/docs/index.js +2 -0
  45. data/app/pb_kits/playbook/pb_table/index.ts +29 -27
  46. data/app/pb_kits/playbook/pb_text_input/text_input.html.erb +10 -10
  47. data/app/pb_kits/playbook/pb_typeahead/_typeahead.tsx +410 -323
  48. data/app/pb_kits/playbook/pb_typeahead/components/Control.tsx +2 -0
  49. data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_required_indicator.html.erb +16 -0
  50. data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_required_indicator.jsx +23 -0
  51. data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_required_indicator.md +3 -0
  52. data/app/pb_kits/playbook/pb_typeahead/docs/example.yml +2 -0
  53. data/app/pb_kits/playbook/pb_typeahead/docs/index.js +22 -21
  54. data/app/pb_kits/playbook/pb_typeahead/typeahead.html.erb +3 -2
  55. data/app/pb_kits/playbook/pb_typeahead/typeahead.rb +3 -1
  56. data/dist/chunks/{_pb_line_graph-BgKF_zz1.js → _pb_line_graph-DuJNCf7N.js} +1 -1
  57. data/dist/chunks/_typeahead-BKSzddAX.js +1 -0
  58. data/dist/chunks/{globalProps-BhVYCqRf.js → globalProps-Bc-FVsRt.js} +1 -1
  59. data/dist/chunks/lib-BwX82vim.js +29 -0
  60. data/dist/chunks/vendor.js +3 -3
  61. data/dist/menu.yml +1 -1
  62. data/dist/playbook-rails-react-bindings.js +1 -1
  63. data/dist/playbook-rails.js +1 -1
  64. data/dist/playbook.css +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 +19 -6
  70. data/dist/chunks/_typeahead-CWA5wlah.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: 4716e526924fd62bb0c877a51c1b135e4d881bb50c2995a67a51aa3c667bf320
4
- data.tar.gz: d8e539ed648f227860fee870d7c8b80ea9a6ba2cf9e0420c59cd192bf58dae50
3
+ metadata.gz: 57e7920a7b47285ec3e254c7c67f5eda6d1742c4b51455bff71452d431bbaa36
4
+ data.tar.gz: 6894b82d417daccc631daed96d00ee1462bf76944958c4ec2226f9a0fb588463
5
5
  SHA512:
6
- metadata.gz: 8d2b0e61996919c9d3234518bf9ce2708f3be86de41039abf8a820029d22063a2ebdceb41a183852f5ab88315a44c6bd20daeba2bb1a95ec3dcb2aa59cf74d1a
7
- data.tar.gz: b5b94f8ead24d755a3d0c1a10fe6e2175c45eb874fe413705a99d5740b82a25be3b44abb98a1c22c41da6562f61cd1815eea65622ad97686e7a9f07069d573cc
6
+ metadata.gz: 4b48cd0af7cc499e37ef1f2cc702c06f26b5865095b362da32f23e80eeb8aef75bae49c79e29b38d0c8bbe2790564896ebb92496196d639ae351d0579f57ca16
7
+ data.tar.gz: 5a57afcd4dfa17ef354d30b48c8d8a501e30e86283e343723fa63fee9395c115288b47d8a9ee68848d233e5ea0a96ae81cceb91bf0c21dde21e20608c851a002
@@ -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'
@@ -119,6 +119,16 @@ let Dropdown = (props: DropdownProps, ref: any): React.ReactElement | null => {
119
119
 
120
120
  const [isDropDownClosed, setIsDropDownClosed, toggleDropdown] = useDropdown(isClosed);
121
121
 
122
+ // Use a suffix for the trigger ID to avoid conflict with the outer div's id
123
+ const sanitizeForId = (str: string) =>
124
+ str.toLowerCase().replace(/\s+/g, "_").replace(/[^a-z0-9_]/g, "");
125
+ const selectId = id
126
+ ? `${id}_trigger`
127
+ : label
128
+ ? sanitizeForId(label)
129
+ : undefined;
130
+ const errorId = error ? `${selectId}-error` : undefined;
131
+
122
132
  const [filterItem, setFilterItem] = useState("");
123
133
  const initialSelected = useMemo(() => {
124
134
  // Handle quickpick variant with string defaultValue (e.g., "This Month")
@@ -151,9 +161,19 @@ let Dropdown = (props: DropdownProps, ref: any): React.ReactElement | null => {
151
161
 
152
162
  const dropdownRef = useRef(null);
153
163
  const inputRef = useRef<HTMLInputElement>(null);
154
- const inputWrapperRef = useRef(null);
164
+ const inputWrapperRef = useRef<HTMLDivElement | null>(null);
155
165
  const dropdownContainerRef = useRef(null);
156
166
 
167
+ const handleLabelClick = (e: React.MouseEvent) => {
168
+ e.stopPropagation();
169
+ if (selectId) {
170
+ const trigger = document.getElementById(selectId);
171
+ if (trigger) trigger.focus();
172
+ }
173
+ setIsInputFocused(true);
174
+ toggleDropdown();
175
+ };
176
+
157
177
  const selectedArray = Array.isArray(selected)
158
178
  ? selected
159
179
  : selected && Object.keys(selected).length
@@ -411,9 +431,12 @@ let Dropdown = (props: DropdownProps, ref: any): React.ReactElement | null => {
411
431
  autocomplete,
412
432
  clearable,
413
433
  dropdownContainerRef,
414
- filteredOptions,
434
+ error,
435
+ errorId,
415
436
  filterItem,
437
+ filteredOptions,
416
438
  focusedOptionIndex,
439
+ label,
417
440
  formPillProps,
418
441
  handleBackspace,
419
442
  handleChange,
@@ -423,6 +446,7 @@ let Dropdown = (props: DropdownProps, ref: any): React.ReactElement | null => {
423
446
  inputWrapperRef,
424
447
  isDropDownClosed,
425
448
  isInputFocused,
449
+ selectId,
426
450
  multiSelect,
427
451
  onSelect,
428
452
  optionsWithBlankSelection,
@@ -434,13 +458,20 @@ let Dropdown = (props: DropdownProps, ref: any): React.ReactElement | null => {
434
458
  toggleDropdown
435
459
  }}
436
460
  >
437
- {label &&
438
- <Caption
439
- dark={dark}
440
- marginBottom="xs"
441
- text={label}
442
- />
443
- }
461
+ {label && (
462
+ <label
463
+ data-dropdown="pb-dropdown-label"
464
+ htmlFor={selectId}
465
+ onClick={handleLabelClick}
466
+ >
467
+ <Caption
468
+ className="pb_dropdown_kit_label"
469
+ dark={dark}
470
+ marginBottom="xs"
471
+ text={label}
472
+ />
473
+ </label>
474
+ )}
444
475
  <div className={`dropdown_wrapper ${error ? 'error' : ''}`}
445
476
  onBlur={() => {
446
477
  // Debounce to delay the execution to prevent jumpiness in Focus state
@@ -473,12 +504,16 @@ let Dropdown = (props: DropdownProps, ref: any): React.ReactElement | null => {
473
504
  </>
474
505
  )}
475
506
 
476
- {error &&
507
+ {error && (
477
508
  <Body
509
+ aria={{ atomic: "true", live: "polite" }}
510
+ dark={dark}
511
+ htmlOptions={{ role: "alert" }}
512
+ id={errorId}
478
513
  status="negative"
479
514
  text={error}
480
515
  />
481
- }
516
+ )}
482
517
  </div>
483
518
  </DropdownContext.Provider>
484
519
  </div>
@@ -1,10 +1,13 @@
1
- <%
1
+ <%
2
2
  options = [
3
3
  { label: 'United States', value: 'unitedStates', id: 'us' },
4
4
  { label: 'Canada', value: 'canada', id: 'ca' },
5
5
  { label: 'Pakistan', value: 'pakistan', id: 'pk' },
6
6
  ]
7
-
8
7
  %>
9
8
 
10
- <%= pb_rails("dropdown", props: {options: options, label: "Select a Country"}) %>
9
+ <%= pb_rails("dropdown", props: {
10
+ id: "select_a_country",
11
+ label: "Select a Country",
12
+ options: options
13
+ }) %>
@@ -25,6 +25,7 @@ const DropdownDefault = (props) => {
25
25
  return (
26
26
  <div>
27
27
  <Dropdown
28
+ id="select_a_country"
28
29
  label="Select a Country"
29
30
  options={options}
30
31
  {...props}
@@ -1 +1,3 @@
1
- The top-level Dropdown component optionally accepts any string through a `label` prop to produce a label above your trigger element.
1
+ The top-level Dropdown component optionally accepts any string through a `label` prop to produce a label above your trigger element.
2
+
3
+ Add an `id` to wire the label to the trigger so that clicking the label will move focus directly to the input, and open the drop-down.
@@ -1,6 +1,8 @@
1
1
  <%= pb_content_tag do %>
2
2
  <% if object.label.present? %>
3
- <%= pb_rails("caption", props: {text: object.label, margin_bottom:"xs"}) %>
3
+ <label for="<%= object.select_id %>" data-dropdown="pb-dropdown-label">
4
+ <%= pb_rails("caption", props: { text: object.label, margin_bottom: "xs", classname: "pb_dropdown_kit_label", dark: object.dark }) %>
5
+ </label>
4
6
  <% end %>
5
7
  <div class="dropdown_wrapper<%= error_class %>" style="position: relative">
6
8
  <input
@@ -16,9 +18,11 @@
16
18
  <% end %>
17
19
  <% if content.present? %>
18
20
  <%= content.presence %>
19
- <%= pb_rails("body", props: { status: "negative", text: object.error }) %>
21
+ <% if object.error.present? %>
22
+ <%= pb_rails("body", props: { status: "negative", text: object.error, id: object.error_id, aria: { atomic: "true", live: "polite" }, html_options: { role: "alert" }, dark: object.dark }) %>
23
+ <% end %>
20
24
  <% else %>
21
- <%= pb_rails("dropdown/dropdown_trigger", props:{ autocomplete: object.autocomplete, multi_select:object.multi_select, placeholder: object.placeholder }) %>
25
+ <%= pb_rails("dropdown/dropdown_trigger", props: { autocomplete: object.autocomplete, multi_select: object.multi_select, placeholder: object.placeholder, select_id: object.select_id, label: object.label, error_id: object.error_id, error: object.error }) %>
22
26
  <%= pb_rails("dropdown/dropdown_container", props: { searchbar: object.searchbar, constrain_height: object.constrain_height }) do %>
23
27
  <% if options_with_blank.present? %>
24
28
  <% options_with_blank.each do |option| %>
@@ -27,7 +31,9 @@
27
31
  <% end %>
28
32
  <% end %>
29
33
 
30
- <%= pb_rails("body", props: { status: "negative", text: object.error }) %>
34
+ <% if object.error.present? %>
35
+ <%= pb_rails("body", props: { status: "negative", text: object.error, id: object.error_id, aria: { atomic: "true", live: "polite" }, html_options: { role: "alert" }, dark: object.dark }) %>
36
+ <% end %>
31
37
  <% end %>
32
38
  </div>
33
39
  <% end %>
@@ -10,6 +10,7 @@ module Playbook
10
10
  prop :label, type: Playbook::Props::String
11
11
  prop :name, type: Playbook::Props::String
12
12
  prop :error, type: Playbook::Props::String
13
+ prop :id, type: Playbook::Props::String
13
14
  prop :required, type: Playbook::Props::Boolean,
14
15
  default: false
15
16
  prop :default_value
@@ -68,6 +69,14 @@ module Playbook
68
69
  generate_classname("pb_dropdown", variant, separators_class)
69
70
  end
70
71
 
72
+ def select_id
73
+ id.presence || (label.present? ? label.downcase.gsub(/\s+/, "_").gsub(/[^a-z0-9_]/, "") : nil)
74
+ end
75
+
76
+ def error_id
77
+ error.present? ? "#{select_id || 'dropdown_trigger'}-error" : nil
78
+ end
79
+
71
80
  private
72
81
 
73
82
  def error_class
@@ -1,6 +1,10 @@
1
1
  <%= pb_content_tag do %>
2
2
  <% if content.present? %>
3
- <div style="display: inline-block" tabindex="0" data-dropdown-custom-trigger>
3
+ <div style="display: inline-block" tabindex="0" data-dropdown="pb-dropdown-trigger" data-dropdown-custom-trigger aria-invalid="<%= object.error.present? %>"
4
+ <% if object.label.present? %> aria-label="<%= [object.label, object.default_display_placeholder].join(', ') %>"<% end %>
5
+ <% if object.select_id.present? %> id="<%= object.select_id %>"<% end %>
6
+ <% if object.error_id.present? %> aria-describedby="<%= object.error_id %>"<% end %>
7
+ >
4
8
  <%= content.presence %>
5
9
  </div>
6
10
  <% else %>
@@ -9,10 +13,11 @@
9
13
  border_radius:"lg",
10
14
  classname: object.trigger_wrapper_classes,
11
15
  cursor: object.autocomplete ? "text" : "pointer",
16
+ id: object.select_id,
12
17
  justify: "between",
13
18
  padding_x:"sm",
14
19
  padding_y:"xs",
15
- html_options: {tabindex:"0"}
20
+ html_options: { tabindex: "0", "aria-label": object.label.present? ? [object.label, object.default_display_placeholder].join(", ") : nil, "aria-describedby": object.error_id, "aria-invalid": object.error.present?, "data-dropdown": "pb-dropdown-trigger" }
16
21
  }) do %>
17
22
  <%= pb_rails("flex/flex_item", props: { fixed_size: object.multi_select ? "85%" : "" }) do %>
18
23
  <%= pb_rails("flex", props: {align: "center"}) do %>
@@ -13,6 +13,10 @@ module Playbook
13
13
  default: false
14
14
  prop :multi_select, type: Playbook::Props::Boolean,
15
15
  default: false
16
+ prop :select_id, type: Playbook::Props::String
17
+ prop :label, type: Playbook::Props::String
18
+ prop :error_id, type: Playbook::Props::String
19
+ prop :error, type: Playbook::Props::String
16
20
 
17
21
  def data
18
22
  Hash(prop(:data)).merge(dropdown_trigger: true, dropdown_placeholder: default_display_placeholder)