playbook_ui 14.19.0 → 14.20.0.pre.alpha.revert4453PBNTR933reactdraggablebugdragbtwnexamples7854

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 (131) hide show
  1. checksums.yaml +4 -4
  2. data/app/pb_kits/playbook/pb_advanced_table/Components/RegularTableView.tsx +11 -1
  3. data/app/pb_kits/playbook/pb_advanced_table/Components/TableActionBar.tsx +175 -16
  4. data/app/pb_kits/playbook/pb_advanced_table/Components/TableHeaderCell.tsx +56 -25
  5. data/app/pb_kits/playbook/pb_advanced_table/Hooks/useTableState.ts +23 -13
  6. data/app/pb_kits/playbook/pb_advanced_table/Utilities/VisibilityTree.ts +47 -0
  7. data/app/pb_kits/playbook/pb_advanced_table/_advanced_table.scss +6 -10
  8. data/app/pb_kits/playbook/pb_advanced_table/_advanced_table.tsx +7 -2
  9. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_column_visibility.jsx +57 -0
  10. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_column_visibility.md +4 -0
  11. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_column_visibility_custom.jsx +62 -0
  12. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_column_visibility_custom.md +1 -0
  13. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_column_visibility_multi.jsx +82 -0
  14. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_column_visibility_multi.md +1 -0
  15. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_column_visibility_with_state.jsx +66 -0
  16. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_column_visibility_with_state.md +3 -0
  17. data/app/pb_kits/playbook/pb_advanced_table/docs/example.yml +4 -0
  18. data/app/pb_kits/playbook/pb_advanced_table/docs/index.js +5 -1
  19. data/app/pb_kits/playbook/pb_advanced_table/scss_partials/advanced_table_sticky_mixin.scss +1 -0
  20. data/app/pb_kits/playbook/pb_draggable/context/index.tsx +17 -58
  21. data/app/pb_kits/playbook/pb_dropdown/_dropdown.scss +1 -1
  22. data/app/pb_kits/playbook/pb_dropdown/_dropdown.tsx +77 -19
  23. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_default_rails.html.erb +31 -0
  24. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_default_rails.md +5 -0
  25. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_multi_select.jsx +56 -0
  26. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_multi_select.md +3 -0
  27. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_multi_select_display.jsx +58 -0
  28. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_multi_select_display.md +3 -0
  29. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_multi_select_display_rails.html.erb +20 -0
  30. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_multi_select_display_rails.md +1 -0
  31. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_multi_select_rails.html.erb +19 -0
  32. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_multi_select_rails.md +3 -0
  33. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_multi_select_with_autocomplete.html.erb +20 -0
  34. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_multi_select_with_autocomplete.jsx +57 -0
  35. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_multi_select_with_autocomplete.md +1 -0
  36. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_multi_select_with_custom_options.html.erb +50 -0
  37. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_multi_select_with_custom_options.jsx +105 -0
  38. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_multi_select_with_default.html.erb +22 -0
  39. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_multi_select_with_default.jsx +67 -0
  40. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_custom_display.jsx +11 -0
  41. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_custom_display.md +1 -1
  42. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_custom_display_rails.html.erb +33 -2
  43. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_custom_display_rails.md +3 -1
  44. data/app/pb_kits/playbook/pb_dropdown/docs/example.yml +11 -1
  45. data/app/pb_kits/playbook/pb_dropdown/docs/index.js +5 -0
  46. data/app/pb_kits/playbook/pb_dropdown/dropdown.html.erb +3 -3
  47. data/app/pb_kits/playbook/pb_dropdown/dropdown.rb +16 -2
  48. data/app/pb_kits/playbook/pb_dropdown/dropdown_trigger.html.erb +34 -13
  49. data/app/pb_kits/playbook/pb_dropdown/dropdown_trigger.rb +3 -1
  50. data/app/pb_kits/playbook/pb_dropdown/hooks/useHandleOnKeydown.tsx +0 -6
  51. data/app/pb_kits/playbook/pb_dropdown/index.js +336 -30
  52. data/app/pb_kits/playbook/pb_dropdown/keyboard_accessibility.js +39 -12
  53. data/app/pb_kits/playbook/pb_dropdown/subcomponents/DropdownContainer.tsx +2 -2
  54. data/app/pb_kits/playbook/pb_dropdown/subcomponents/DropdownOption.tsx +16 -12
  55. data/app/pb_kits/playbook/pb_dropdown/subcomponents/DropdownTrigger.tsx +79 -13
  56. data/app/pb_kits/playbook/pb_dropdown/subcomponents/MultiSelectTriggerDisplay.tsx +58 -0
  57. data/app/pb_kits/playbook/pb_file_upload/_file_upload.scss +13 -0
  58. data/app/pb_kits/playbook/pb_file_upload/_file_upload.tsx +11 -1
  59. data/app/pb_kits/playbook/pb_file_upload/docs/_file_upload_error.html.erb +1 -0
  60. data/app/pb_kits/playbook/pb_file_upload/docs/_file_upload_error.jsx +41 -0
  61. data/app/pb_kits/playbook/pb_file_upload/docs/example.yml +2 -0
  62. data/app/pb_kits/playbook/pb_file_upload/docs/index.js +1 -0
  63. data/app/pb_kits/playbook/pb_file_upload/file_upload.html.erb +1 -0
  64. data/app/pb_kits/playbook/pb_file_upload/file_upload.rb +7 -1
  65. data/app/pb_kits/playbook/pb_file_upload/fileupload.test.js +18 -0
  66. data/app/pb_kits/playbook/pb_form/docs/_form_form_with.html.erb +1 -0
  67. data/app/pb_kits/playbook/pb_form/docs/_form_form_with_validate.html.erb +1 -0
  68. data/app/pb_kits/playbook/pb_form_group/_error_state_mixin.scss +2 -2
  69. data/app/pb_kits/playbook/pb_form_pill/_form_pill.scss +19 -12
  70. data/app/pb_kits/playbook/pb_home_address_street/_home_address_street.tsx +13 -7
  71. data/app/pb_kits/playbook/pb_multi_level_select/_multi_level_select.tsx +2 -2
  72. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_color.html.erb +11 -11
  73. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_color.jsx +11 -11
  74. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_default.html.erb +11 -11
  75. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_default.jsx +11 -11
  76. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_disabled.html.erb +11 -11
  77. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_disabled.jsx +11 -11
  78. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_disabled_options.html.erb +11 -11
  79. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_disabled_options.jsx +11 -11
  80. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_disabled_options_default.html.erb +11 -11
  81. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_disabled_options_default.jsx +11 -11
  82. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_disabled_options_parent.html.erb +11 -11
  83. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_disabled_options_parent.jsx +11 -11
  84. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_disabled_options_parent_default.html.erb +11 -11
  85. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_disabled_options_parent_default.jsx +11 -11
  86. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_error.html.erb +11 -11
  87. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_error.jsx +11 -11
  88. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_label.html.erb +11 -11
  89. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_label.jsx +11 -11
  90. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_react_hook.jsx +11 -11
  91. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_reset.html.erb +11 -11
  92. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_return_all_selected.html.erb +11 -11
  93. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_return_all_selected.jsx +11 -11
  94. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_selected_ids.html.erb +11 -11
  95. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_selected_ids.md +2 -0
  96. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_selected_ids_react.jsx +11 -11
  97. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_selected_ids_react.md +3 -1
  98. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_single.html.erb +22 -22
  99. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_single.jsx +22 -22
  100. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_single_children_only.html.erb +22 -22
  101. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_single_children_only.jsx +22 -22
  102. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_with_children.jsx +11 -11
  103. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_with_children_with_radios.jsx +11 -11
  104. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_with_form.html.erb +11 -11
  105. data/app/pb_kits/playbook/pb_person/_person.tsx +12 -2
  106. data/app/pb_kits/playbook/pb_phone_number_input/_phone_number_input.scss +9 -9
  107. data/app/pb_kits/playbook/pb_section_separator/_section_separator.tsx +2 -2
  108. data/app/pb_kits/playbook/pb_text_input/_text_input.scss +4 -2
  109. data/app/pb_kits/playbook/pb_typeahead/_typeahead.tsx +73 -3
  110. data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_preserve_input.jsx +23 -0
  111. data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_preserve_input.md +1 -0
  112. data/app/pb_kits/playbook/pb_typeahead/docs/example.yml +1 -0
  113. data/app/pb_kits/playbook/pb_typeahead/docs/index.js +1 -0
  114. data/dist/chunks/_typeahead-C-CI5Vgw.js +22 -0
  115. data/dist/chunks/_weekday_stacked-BCiM3zWM.js +45 -0
  116. data/dist/chunks/lazysizes-B7xYodB-.js +1 -0
  117. data/dist/chunks/lib-D5R1BjUn.js +29 -0
  118. data/dist/chunks/{pb_form_validation-BioH7DWv.js → pb_form_validation-BZ2AVAi_.js} +1 -1
  119. data/dist/chunks/vendor.js +1 -1
  120. data/dist/playbook-doc.js +2 -2
  121. data/dist/playbook-rails-react-bindings.js +1 -1
  122. data/dist/playbook-rails.js +1 -1
  123. data/dist/playbook.css +1 -1
  124. data/lib/playbook/kit_base.rb +3 -3
  125. data/lib/playbook/version.rb +2 -2
  126. metadata +38 -8
  127. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_default.html.erb +0 -10
  128. data/dist/chunks/_typeahead-D62OcwsT.js +0 -22
  129. data/dist/chunks/_weekday_stacked-Ceh9N0ow.js +0 -45
  130. data/dist/chunks/lazysizes-DHz07jlL.js +0 -1
  131. data/dist/chunks/lib-CeKZrPmu.js +0 -29
@@ -0,0 +1,62 @@
1
+ import React from "react"
2
+ import AdvancedTable from '../../pb_advanced_table/_advanced_table'
3
+ import MOCK_DATA from "./advanced_table_mock_data.json"
4
+
5
+ const AdvancedTableColumnVisibilityCustom = (props) => {
6
+ const columnDefinitions = [
7
+ {
8
+ accessor: "year",
9
+ label: "Year",
10
+ cellAccessors: ["quarter", "month", "day"],
11
+ id: "year"
12
+ },
13
+ {
14
+ accessor: "newEnrollments",
15
+ label: "New Enrollments",
16
+ id: "newEnrollments"
17
+ },
18
+ {
19
+ accessor: "scheduledMeetings",
20
+ label: "Scheduled Meetings",
21
+ id: "scheduledMeetings"
22
+ },
23
+ {
24
+ accessor: "attendanceRate",
25
+ label: "Attendance Rate",
26
+ id: "attendanceRate"
27
+ },
28
+ {
29
+ accessor: "completedClasses",
30
+ label: "Completed Classes",
31
+ id: "completedClasses"
32
+ },
33
+ {
34
+ accessor: "classCompletionRate",
35
+ label: "Class Completion Rate",
36
+ id: "classCompletionRate"
37
+ },
38
+ {
39
+ accessor: "graduatedStudents",
40
+ label: "Graduated Students",
41
+ id: "graduatedStudents"
42
+ },
43
+ ]
44
+
45
+ const columnVisibilityControl = {
46
+ // This is the list of column ids that will be included in the column visibility control
47
+ includeIds:["newEnrollments", "scheduledMeetings", "attendanceRate", "completedClasses"],
48
+ }
49
+
50
+ return (
51
+ <div>
52
+ <AdvancedTable
53
+ columnDefinitions={columnDefinitions}
54
+ columnVisibilityControl={columnVisibilityControl}
55
+ tableData={MOCK_DATA}
56
+ {...props}
57
+ />
58
+ </div>
59
+ )
60
+ }
61
+
62
+ export default AdvancedTableColumnVisibilityCustom
@@ -0,0 +1 @@
1
+ By using the `includeIds` key/value pair as shown within the `columnVisibilityControl` prop, you can control which columns show up as options in the columnVisibility dropdown.
@@ -0,0 +1,82 @@
1
+ import React from "react"
2
+ import AdvancedTable from '../../pb_advanced_table/_advanced_table'
3
+ import MOCK_DATA from "./advanced_table_mock_data.json"
4
+
5
+ const AdvancedTableColumnVisibilityMulti = (props) => {
6
+ const columnDefinitions = [
7
+ {
8
+ accessor: "year",
9
+ label: "Year",
10
+ id: "year",
11
+ cellAccessors: ["quarter", "month", "day"],
12
+ },
13
+ {
14
+ label: "Enrollment Data",
15
+ id: "enrollmentData",
16
+ columns: [
17
+ {
18
+ label: "Enrollment Stats",
19
+ id: "enrollmentStats",
20
+ columns: [
21
+ {
22
+ accessor: "newEnrollments",
23
+ label: "New Enrollments",
24
+ id: "newEnrollments",
25
+ },
26
+ {
27
+ accessor: "scheduledMeetings",
28
+ label: "Scheduled Meetings",
29
+ id: "scheduledMeetings",
30
+ },
31
+ ],
32
+ },
33
+ ],
34
+ },
35
+ {
36
+ label: "Performance Data",
37
+ id: "performanceData",
38
+ columns: [
39
+ {
40
+ label: "Completion Metrics",
41
+ id: "completionMetrics",
42
+ columns: [
43
+ {
44
+ accessor: "completedClasses",
45
+ label: "Completed Classes",
46
+ id: "completedClasses",
47
+ },
48
+ {
49
+ accessor: "classCompletionRate",
50
+ label: "Class Completion Rate",
51
+ id: "classCompletionRate",
52
+ },
53
+ ],
54
+ },
55
+ {
56
+ label: "Attendance",
57
+ id: "attendance",
58
+ columns: [
59
+ {
60
+ accessor: "attendanceRate",
61
+ label: "Attendance Rate",
62
+ id: "attendanceRate",
63
+ },
64
+ ],
65
+ },
66
+ ],
67
+ },
68
+ ];
69
+
70
+ return (
71
+ <div>
72
+ <AdvancedTable
73
+ columnDefinitions={columnDefinitions}
74
+ columnVisibilityControl={{default: true}}
75
+ tableData={MOCK_DATA}
76
+ {...props}
77
+ />
78
+ </div>
79
+ )
80
+ }
81
+
82
+ export default AdvancedTableColumnVisibilityMulti
@@ -0,0 +1 @@
1
+ The `columnVisibilityControl` prop can also be used with multi-header columns as shown.
@@ -0,0 +1,66 @@
1
+ import React, { useState } from "react"
2
+ import AdvancedTable from '../../pb_advanced_table/_advanced_table'
3
+ import MOCK_DATA from "./advanced_table_mock_data.json"
4
+
5
+ const AdvancedTableColumnVisibilityWithState = (props) => {
6
+ const columnDefinitions = [
7
+ {
8
+ accessor: "year",
9
+ label: "Year",
10
+ cellAccessors: ["quarter", "month", "day"],
11
+ id: "year"
12
+ },
13
+ {
14
+ accessor: "newEnrollments",
15
+ label: "New Enrollments",
16
+ id: "newEnrollments"
17
+ },
18
+ {
19
+ accessor: "scheduledMeetings",
20
+ label: "Scheduled Meetings",
21
+ id: "scheduledMeetings"
22
+ },
23
+ {
24
+ accessor: "attendanceRate",
25
+ label: "Attendance Rate",
26
+ id: "attendanceRate"
27
+ },
28
+ {
29
+ accessor: "completedClasses",
30
+ label: "Completed Classes",
31
+ id: "completedClasses"
32
+ },
33
+ {
34
+ accessor: "classCompletionRate",
35
+ label: "Class Completion Rate",
36
+ id: "classCompletionRate"
37
+ },
38
+ {
39
+ accessor: "graduatedStudents",
40
+ label: "Graduated Students",
41
+ id: "graduatedStudents"
42
+ },
43
+ ]
44
+
45
+ const [columnVisibility, setColumnVisibility] = useState({
46
+ newEnrollments: false
47
+ })
48
+
49
+ const columnVisibilityControl = {
50
+ value: columnVisibility,
51
+ onChange: setColumnVisibility,
52
+ onColumnVisibilityChange: (currentState) => console.log(currentState),
53
+ }
54
+ return (
55
+ <div>
56
+ <AdvancedTable
57
+ columnDefinitions={columnDefinitions}
58
+ columnVisibilityControl={columnVisibilityControl}
59
+ tableData={MOCK_DATA}
60
+ {...props}
61
+ />
62
+ </div>
63
+ )
64
+ }
65
+
66
+ export default AdvancedTableColumnVisibilityWithState
@@ -0,0 +1,3 @@
1
+ The `columnVisibilityControl` prop also allows for greater control over the columnVisibility state. Devs can manage state themselves by passing in `value` and `onChange` as shown.
2
+
3
+ The additional `onColumnVisibilityChange` provides a callback with the current state as the argument if needed.
@@ -47,3 +47,7 @@ examples:
47
47
  - advanced_table_selectable_rows_header: Selectable Rows (No Actions Bar)
48
48
  - advanced_table_inline_editing: Inline Cell Editing
49
49
  - advanced_table_fullscreen: Fullscreen
50
+ - advanced_table_column_visibility: Column Visibility Control
51
+ - advanced_table_column_visibility_with_state: Column Visibility Control With State
52
+ - advanced_table_column_visibility_custom: Column Visibility Control with Custom Dropdown
53
+ - advanced_table_column_visibility_multi: Column Visibility Control with Multi-Header Columns
@@ -27,4 +27,8 @@ export { default as AdvancedTableStickyColumns } from './_advanced_table_sticky_
27
27
  export { default as AdvancedTableStickyHeader } from './_advanced_table_sticky_header.jsx'
28
28
  export { default as AdvancedTableStickyColumnsAndHeader } from './_advanced_table_sticky_columns_and_header.jsx'
29
29
  export { default as AdvancedTableExpandByDepth } from './_advanced_table_expand_by_depth.jsx'
30
- export { default as AdvancedTableColumnBorderColor} from './_advanced_table_column_border_color.jsx'
30
+ export { default as AdvancedTableColumnBorderColor} from './_advanced_table_column_border_color.jsx'
31
+ export { default as AdvancedTableColumnVisibility } from './_advanced_table_column_visibility.jsx'
32
+ export { default as AdvancedTableColumnVisibilityCustom } from './_advanced_table_column_visibility_custom.jsx'
33
+ export { default as AdvancedTableColumnVisibilityMulti } from './_advanced_table_column_visibility_multi.jsx'
34
+ export { default as AdvancedTableColumnVisibilityWithState } from './_advanced_table_column_visibility_with_state.jsx'
@@ -77,6 +77,7 @@
77
77
  top: 0;
78
78
  left: 0;
79
79
  border-radius: unset;
80
+ z-index: 5;
80
81
  }
81
82
 
82
83
  .checkbox-cell {
@@ -1,11 +1,11 @@
1
- import React, { createContext, useReducer, useContext, useEffect, useMemo, useRef, useState } from "react";
1
+ import React, { createContext, useReducer, useContext, useEffect, useMemo } from "react";
2
2
  import { InitialStateType, ActionType, DraggableProviderType } from "./types";
3
3
 
4
4
  const initialState: InitialStateType = {
5
5
  items: [],
6
6
  dragData: { id: "", initialGroup: "" },
7
7
  isDragging: "",
8
- activeContainer: "",
8
+ activeContainer: ""
9
9
  };
10
10
 
11
11
  const reducer = (state: InitialStateType, action: ActionType) => {
@@ -31,23 +31,9 @@ const reducer = (state: InitialStateType, action: ActionType) => {
31
31
  const { dragId, targetId } = action.payload;
32
32
  const newItems = [...state.items];
33
33
  const draggedItem = newItems.find(item => item.id === dragId);
34
- const targetItem = newItems.find(item => item.id === targetId);
35
-
36
- if (!draggedItem || !targetItem || draggedItem.container !== targetItem.container) {
37
- return state;
38
- }
39
-
40
- if (dragId === targetId) {
41
- return state;
42
- }
43
-
44
- const draggedIndex = newItems.findIndex(item => item.id === dragId);
34
+ const draggedIndex = newItems.indexOf(draggedItem);
45
35
  const targetIndex = newItems.findIndex(item => item.id === targetId);
46
36
 
47
- if (draggedIndex === -1 || targetIndex === -1) {
48
- return state;
49
- }
50
-
51
37
  newItems.splice(draggedIndex, 1);
52
38
  newItems.splice(targetIndex, 0, draggedItem);
53
39
 
@@ -62,11 +48,7 @@ const reducer = (state: InitialStateType, action: ActionType) => {
62
48
  const DragContext = createContext<any>({});
63
49
 
64
50
  export const DraggableContext = () => {
65
- const context = useContext(DragContext);
66
- if (context === undefined) {
67
- throw new Error('DraggableContext must be used within a DraggableProvider');
68
- }
69
- return context;
51
+ return useContext(DragContext);
70
52
  };
71
53
 
72
54
  export const DraggableProvider = ({
@@ -81,11 +63,7 @@ export const DraggableProvider = ({
81
63
  dropZone = { type: 'ghost', color: 'neutral', direction: 'vertical' }
82
64
  }: DraggableProviderType) => {
83
65
  const [state, dispatch] = useReducer(reducer, initialState);
84
-
85
- // Store initial items in a ref to use if needed (for consistency when needed in future updates)
86
- const initialItemsRef = useRef(initialItems);
87
- const [isDragging, setIsDragging] = useState(false);
88
-
66
+
89
67
  // Parse dropZone prop - handle both string format (backward compatibility) and object format
90
68
  let dropZoneType = 'ghost';
91
69
  let dropZoneColor = 'neutral';
@@ -108,64 +86,45 @@ export const DraggableProvider = ({
108
86
 
109
87
  useEffect(() => {
110
88
  dispatch({ type: 'SET_ITEMS', payload: initialItems });
111
- initialItemsRef.current = initialItems;
112
89
  }, [initialItems]);
113
90
 
114
91
  useEffect(() => {
115
- if (onReorder) {
116
- onReorder(state.items);
117
- }
118
- }, [state.items, onReorder]);
92
+ onReorder(state.items);
93
+ }, [state.items]);
119
94
 
120
95
  const handleDragStart = (id: string, container: string) => {
121
- setIsDragging(true);
122
- dispatch({ type: 'SET_DRAG_DATA', payload: { id, initialGroup: container } });
96
+ dispatch({ type: 'SET_DRAG_DATA', payload: { id: id, initialGroup: container } });
123
97
  dispatch({ type: 'SET_IS_DRAGGING', payload: id });
124
- dispatch({ type: 'SET_ACTIVE_CONTAINER', payload: container });
125
98
  if (onDragStart) onDragStart(id, container);
126
99
  };
127
100
 
128
101
  const handleDragEnter = (id: string, container: string) => {
129
- if (!isDragging || container !== state.activeContainer) return;
130
-
131
- if (state.dragData.id === id) return;
132
-
133
- const draggedItem = state.items.find(item => item.id === state.dragData.id);
134
- const targetItem = state.items.find(item => item.id === id);
135
-
136
- if (!draggedItem || !targetItem || draggedItem.container !== targetItem.container) {
137
- return;
102
+ if (state.dragData.id !== id) {
103
+ dispatch({ type: 'REORDER_ITEMS', payload: { dragId: state.dragData.id, targetId: id } });
104
+ dispatch({ type: 'SET_DRAG_DATA', payload: { id: state.dragData.id, initialGroup: container } });
138
105
  }
139
-
140
- dispatch({ type: 'REORDER_ITEMS', payload: { dragId: state.dragData.id, targetId: id } });
141
-
142
106
  if (onDragEnter) onDragEnter(id, container);
143
107
  };
144
108
 
145
109
  const handleDragEnd = () => {
146
- setIsDragging(false);
147
110
  dispatch({ type: 'SET_IS_DRAGGING', payload: "" });
148
111
  dispatch({ type: 'SET_ACTIVE_CONTAINER', payload: "" });
149
112
  if (onDragEnd) onDragEnd();
150
113
  };
151
114
 
152
- const handleDrop = (container: string) => {
153
- const draggedItem = state.items.find(item => item.id === state.dragData.id);
154
-
155
- if (draggedItem && draggedItem.container !== container) {
156
- dispatch({ type: 'CHANGE_CATEGORY', payload: { itemId: state.dragData.id, container } });
157
- }
115
+ const changeCategory = (itemId: string, container: string) => {
116
+ dispatch({ type: 'CHANGE_CATEGORY', payload: { itemId, container } });
117
+ };
158
118
 
119
+ const handleDrop = (container: string) => {
159
120
  dispatch({ type: 'SET_IS_DRAGGING', payload: "" });
160
121
  dispatch({ type: 'SET_ACTIVE_CONTAINER', payload: "" });
161
-
162
- setIsDragging(false);
122
+ changeCategory(state.dragData.id, container);
163
123
  if (onDrop) onDrop(container);
164
124
  };
165
125
 
166
126
  const handleDragOver = (e: Event, container: string) => {
167
127
  e.preventDefault();
168
- e.stopPropagation();
169
128
  dispatch({ type: 'SET_ACTIVE_CONTAINER', payload: container });
170
129
  if (onDragOver) onDragOver(e, container);
171
130
  };
@@ -185,7 +144,7 @@ export const DraggableProvider = ({
185
144
  handleDragEnd,
186
145
  handleDrop,
187
146
  handleDragOver
188
- }), [state, dropZoneType, dropZoneColor, dropZoneDirection, handleDragStart, handleDragEnter, handleDragEnd, handleDrop, handleDragOver]);
147
+ }), [state, dropZoneType, dropZoneColor, dropZoneDirection]);
189
148
 
190
149
  return (
191
150
  <DragContext.Provider value={contextValue}>{children}</DragContext.Provider>
@@ -15,7 +15,7 @@
15
15
  @include pb_body;
16
16
  border: 1px solid $border_light;
17
17
  background-color: $white;
18
- height: 45px;
18
+ min-height: 45px;
19
19
  @media (hover: hover) {
20
20
  &:hover,
21
21
  &:active,
@@ -1,4 +1,4 @@
1
- import React, { useState, useRef, useEffect, forwardRef, useImperativeHandle } from "react";
1
+ import React, { useState, useRef, useEffect, forwardRef, useImperativeHandle, useMemo } from "react";
2
2
  import classnames from "classnames";
3
3
  import { buildAriaProps, buildCss, buildDataProps, buildHtmlProps } from "../utilities/props";
4
4
  import { globalProps } from "../utilities/globalProps";
@@ -25,6 +25,7 @@ type DropdownProps = {
25
25
  blankSelection?: string;
26
26
  children?: React.ReactChild[] | React.ReactChild | React.ReactElement[];
27
27
  className?: string;
28
+ formPillProps?: GenericObject;
28
29
  dark?: boolean;
29
30
  data?: { [key: string]: string };
30
31
  defaultValue?: GenericObject;
@@ -33,6 +34,7 @@ type DropdownProps = {
33
34
  id?: string;
34
35
  isClosed?: boolean;
35
36
  label?: string;
37
+ multiSelect?: boolean;
36
38
  onSelect?: (arg: GenericObject) => null;
37
39
  options: GenericObject;
38
40
  separators?: boolean;
@@ -61,6 +63,8 @@ let Dropdown = (props: DropdownProps, ref: any): React.ReactElement | null => {
61
63
  id,
62
64
  isClosed = true,
63
65
  label,
66
+ multiSelect = false,
67
+ formPillProps,
64
68
  onSelect,
65
69
  options,
66
70
  separators = true,
@@ -80,7 +84,20 @@ let Dropdown = (props: DropdownProps, ref: any): React.ReactElement | null => {
80
84
  const [isDropDownClosed, setIsDropDownClosed, toggleDropdown] = useDropdown(isClosed);
81
85
 
82
86
  const [filterItem, setFilterItem] = useState("");
83
- const [selected, setSelected] = useState<GenericObject>(defaultValue);
87
+ const initialSelected = useMemo(() => {
88
+ if (multiSelect) {
89
+ if (Array.isArray(defaultValue)) return defaultValue;
90
+ return defaultValue && Object.keys(defaultValue).length
91
+ ? [defaultValue]
92
+ : [];
93
+ }
94
+ return defaultValue || {};
95
+ }, [multiSelect, defaultValue]);
96
+
97
+ const [selected, setSelected] = useState<GenericObject | GenericObject[]>(
98
+ initialSelected
99
+ );
100
+
84
101
  const [isInputFocused, setIsInputFocused] = useState(false);
85
102
  const [hasTriggerSubcomponent, setHasTriggerSubcomponent] = useState(true);
86
103
  const [hasContainerSubcomponent, setHasContainerSubcomponent] =
@@ -93,6 +110,12 @@ let Dropdown = (props: DropdownProps, ref: any): React.ReactElement | null => {
93
110
  const inputWrapperRef = useRef(null);
94
111
  const dropdownContainerRef = useRef(null);
95
112
 
113
+ const selectedArray = Array.isArray(selected)
114
+ ? selected
115
+ : selected && Object.keys(selected).length
116
+ ? [selected]
117
+ : [];
118
+
96
119
  const { trigger, container, otherChildren } =
97
120
  separateChildComponents(children);
98
121
 
@@ -124,16 +147,23 @@ let Dropdown = (props: DropdownProps, ref: any): React.ReactElement | null => {
124
147
 
125
148
  const blankSelectionOption: GenericObject = blankSelection ? [{ label: blankSelection, value: "" }] : [];
126
149
  const optionsWithBlankSelection = blankSelectionOption.concat(options);
127
- const filteredOptions = optionsWithBlankSelection?.filter((option: GenericObject) => {
128
- const label = typeof option.label === 'string' ? option.label.toLowerCase() : option.label;
129
- return String(label).toLowerCase().includes(filterItem.toLowerCase());
130
- });
150
+
151
+ const availableOptions = useMemo(()=> {
152
+ if (!multiSelect) return optionsWithBlankSelection;
153
+ return optionsWithBlankSelection.filter((option: GenericObject) => !selectedArray.some((sel) => sel.label === option.label));
154
+ }, [optionsWithBlankSelection, selectedArray, multiSelect]);
155
+
156
+ const filteredOptions = useMemo(() => {
157
+ return availableOptions.filter((opt: GenericObject) =>
158
+ String(opt.label).toLowerCase().includes(filterItem.toLowerCase())
159
+ );
160
+ }, [availableOptions, filterItem]);
131
161
 
132
162
  // For keyboard accessibility: Set focus within dropdown to selected item if it exists
133
163
  useEffect(() => {
134
164
  if (!isDropDownClosed) {
135
165
  let newIndex = 0;
136
- if (selected && selected?.label) {
166
+ if (selected && !Array.isArray(selected) && selected.label) {
137
167
  const selectedIndex = filteredOptions.findIndex((option: GenericObject) => option.label === selected.label);
138
168
  if (selectedIndex >= 0) {
139
169
  newIndex = selectedIndex;
@@ -149,12 +179,27 @@ let Dropdown = (props: DropdownProps, ref: any): React.ReactElement | null => {
149
179
  setIsDropDownClosed(false);
150
180
  };
151
181
 
152
- const handleOptionClick = (selectedItem: GenericObject) => {
153
- setSelected(selectedItem);
154
- setFilterItem("");
155
- setIsDropDownClosed(true);
156
- onSelect && onSelect(selectedItem);
157
- };
182
+
183
+ const handleOptionClick = (clickedItem: GenericObject) => {
184
+ if (multiSelect) {
185
+ setSelected((prev) => {
186
+ const list = prev as GenericObject[];
187
+ const exists = list.find((option) => option.value === clickedItem.value);
188
+ const next = exists
189
+ ? list.filter((option) => option.value !== clickedItem.value)
190
+ : [...list, clickedItem];
191
+ onSelect && onSelect(next);
192
+ return next;
193
+ });
194
+ setFilterItem("");
195
+ setIsDropDownClosed(true);
196
+ } else {
197
+ setSelected(clickedItem);
198
+ setFilterItem("");
199
+ setIsDropDownClosed(true);
200
+ onSelect && onSelect(clickedItem);
201
+ }
202
+ };
158
203
 
159
204
  const handleWrapperClick = () => {
160
205
  autocomplete && inputRef?.current?.focus();
@@ -162,9 +207,14 @@ let Dropdown = (props: DropdownProps, ref: any): React.ReactElement | null => {
162
207
  };
163
208
 
164
209
  const handleBackspace = () => {
210
+ if (multiSelect) {
211
+ setSelected([]);
212
+ onSelect && onSelect([]);
213
+ } else {
165
214
  setSelected({});
166
215
  onSelect && onSelect(null);
167
216
  setFocusedOptionIndex(-1);
217
+ }
168
218
  };
169
219
 
170
220
  const componentsToRender = prepareSubcomponents({
@@ -178,12 +228,17 @@ let Dropdown = (props: DropdownProps, ref: any): React.ReactElement | null => {
178
228
  });
179
229
 
180
230
  useImperativeHandle(ref, () => ({
181
- clearSelected: () => {
182
- setSelected({});
183
- setFilterItem("");
184
- setIsDropDownClosed(true);
185
- onSelect && onSelect(null);
186
- },
231
+ clearSelected: () => {
232
+ if (multiSelect) {
233
+ setSelected([]);
234
+ onSelect && onSelect([]);
235
+ } else {
236
+ setSelected({});
237
+ onSelect && onSelect(null);
238
+ }
239
+ setFilterItem("");
240
+ setIsDropDownClosed(true);
241
+ },
187
242
  }));
188
243
 
189
244
  return (
@@ -201,6 +256,7 @@ let Dropdown = (props: DropdownProps, ref: any): React.ReactElement | null => {
201
256
  filteredOptions,
202
257
  filterItem,
203
258
  focusedOptionIndex,
259
+ formPillProps,
204
260
  handleBackspace,
205
261
  handleChange,
206
262
  handleOptionClick,
@@ -209,6 +265,8 @@ let Dropdown = (props: DropdownProps, ref: any): React.ReactElement | null => {
209
265
  inputWrapperRef,
210
266
  isDropDownClosed,
211
267
  isInputFocused,
268
+ multiSelect,
269
+ onSelect,
212
270
  optionsWithBlankSelection,
213
271
  selected,
214
272
  setFocusedOptionIndex,
@@ -0,0 +1,31 @@
1
+ <%
2
+ options = [
3
+ { label: 'United States', value: 'United States', id: 'us' },
4
+ { label: 'Canada', value: 'Canada', id: 'ca' },
5
+ { label: 'Pakistan', value: 'Pakistan', id: 'pk' },
6
+ ]
7
+
8
+ %>
9
+
10
+ <%
11
+ options2 = [
12
+ { label: 'India', value: 'India', id: 'in' },
13
+ { label: 'Mexico', value: 'Mexico', id: 'mx' },
14
+ { label: 'Brazil', value: 'Brazil', id: 'br' },
15
+ { label: 'Argentina', value: 'Argentina', id: 'ar' },
16
+ { label: 'Colombia', value: 'Colombia', id: 'co' },
17
+ { label: 'Chile', value: 'Chile', id: 'cl' },
18
+ { label: 'Peru', value: 'Peru', id: 'pe' },
19
+ ]
20
+
21
+ %>
22
+
23
+ <%= pb_rails("dropdown", props: {options: options}) %>
24
+
25
+ <script>
26
+ document.addEventListener("pb:dropdown:selected", (e) => {
27
+ const option = e.detail;
28
+ const dropdown = e.target;
29
+ console.log("Selected option:", option);
30
+ })
31
+ </script>
@@ -0,0 +1,5 @@
1
+ This kit's `options` prop requires an array of objects, each of which will be used as the selectable options within the dropdown. Each option object can support any number of key-value pairs, but each MUST contain `label`, `value` and `id`.
2
+
3
+ The kit also comes with a custom event called "pb:dropdown:selected" which updates dynamically with the selection as it changes. See code snippet to see this in action.
4
+
5
+ In addition, a data attribute called `data-option-selected` with the selection is also rendered on the parent dropdown div.