playbook_ui 14.20.0.pre.alpha.PLAY22297981 → 14.20.0.pre.alpha.PLAY22408048

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 (43) hide show
  1. checksums.yaml +4 -4
  2. data/app/pb_kits/playbook/pb_advanced_table/Components/RegularTableView.tsx +116 -49
  3. data/app/pb_kits/playbook/pb_advanced_table/Context/AdvancedTableContext.tsx +58 -2
  4. data/app/pb_kits/playbook/pb_advanced_table/Hooks/useTableActions.ts +1 -1
  5. data/app/pb_kits/playbook/pb_advanced_table/Hooks/useTableState.ts +16 -4
  6. data/app/pb_kits/playbook/pb_advanced_table/SubKits/TableHeader.tsx +7 -3
  7. data/app/pb_kits/playbook/pb_advanced_table/_advanced_table.scss +13 -0
  8. data/app/pb_kits/playbook/pb_advanced_table/_advanced_table.tsx +10 -3
  9. data/app/pb_kits/playbook/pb_advanced_table/advanced_table.test.jsx +61 -0
  10. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_pinned_rows.jsx +57 -0
  11. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_pinned_rows_react.md +5 -0
  12. data/app/pb_kits/playbook/pb_advanced_table/docs/example.yml +2 -1
  13. data/app/pb_kits/playbook/pb_advanced_table/docs/index.js +2 -1
  14. data/app/pb_kits/playbook/pb_advanced_table/index.js +2 -0
  15. data/app/pb_kits/playbook/pb_checkbox/checkbox.html.erb +4 -11
  16. data/app/pb_kits/playbook/pb_checkbox/checkbox.rb +10 -6
  17. data/app/pb_kits/playbook/pb_checkbox/docs/_checkbox_indeterminate.html.erb +2 -48
  18. data/app/pb_kits/playbook/pb_checkbox/docs/_checkbox_indeterminate_rails.md +1 -0
  19. data/app/pb_kits/playbook/pb_checkbox/index.js +56 -0
  20. data/app/pb_kits/playbook/pb_date_picker/date_picker_helper.ts +13 -4
  21. data/app/pb_kits/playbook/pb_draggable/context/index.tsx +17 -58
  22. data/app/pb_kits/playbook/pb_dropdown/_dropdown.tsx +12 -3
  23. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_close_on_select.jsx +42 -0
  24. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_close_on_select.md +1 -0
  25. data/app/pb_kits/playbook/pb_dropdown/docs/example.yml +2 -0
  26. data/app/pb_kits/playbook/pb_dropdown/docs/index.js +2 -1
  27. data/app/pb_kits/playbook/pb_dropdown/subcomponents/DropdownOption.tsx +14 -10
  28. data/app/pb_kits/playbook/pb_dropdown/subcomponents/DropdownTrigger.tsx +26 -15
  29. data/app/pb_kits/playbook/pb_table/docs/_table_with_selectable_rows.html.erb +3 -51
  30. data/app/pb_kits/playbook/pb_table/styles/_mobile_collapse.scss +1 -1
  31. data/dist/chunks/{_typeahead-DsBjTkC_.js → _typeahead-C0OGBz_9.js} +1 -1
  32. data/dist/chunks/_weekday_stacked-BbY_mMKV.js +45 -0
  33. data/dist/chunks/{lib-D7Va7yqa.js → lib-CLDGEByA.js} +1 -1
  34. data/dist/chunks/{pb_form_validation-DSkdRDMf.js → pb_form_validation-C-ccDsK6.js} +1 -1
  35. data/dist/chunks/vendor.js +1 -1
  36. data/dist/menu.yml +1 -1
  37. data/dist/playbook-doc.js +1 -1
  38. data/dist/playbook-rails-react-bindings.js +1 -1
  39. data/dist/playbook-rails.js +1 -1
  40. data/dist/playbook.css +1 -1
  41. data/lib/playbook/version.rb +1 -1
  42. metadata +12 -6
  43. data/dist/chunks/_weekday_stacked-P46uSudZ.js +0 -45
@@ -1,16 +1,9 @@
1
1
  <%= pb_content_tag(:label) do %>
2
2
  <%= content.presence || object.input %>
3
- <% if object.indeterminate %>
4
- <span data-pb-checkbox-icon-span="true" class="pb_checkbox_indeterminate">
5
- <%= pb_rails("icon", props: { icon: "minus", classname: "indeterminate_icon", fixed_width: true}) %>
6
- <%= pb_rails("icon", props: { icon: "check", classname: "check_icon hidden", fixed_width: true}) %>
7
- </span>
8
- <% else %>
9
- <span data-pb-checkbox-icon-span="true" class="pb_checkbox_checkmark">
10
- <%= pb_rails("icon", props: { icon: "check", classname: "check_icon", fixed_width: true}) %>
11
- <%= pb_rails("icon", props: { icon: "minus", classname: "indeterminate_icon hidden", fixed_width: true}) %>
12
- </span>
13
- <% end %>
3
+ <span data-pb-checkbox-icon-span="true" class="pb_checkbox_checkmark">
4
+ <%= pb_rails("icon", props: { icon: "check", classname: "check_icon", fixed_width: true}) %>
5
+ <%= pb_rails("icon", props: { icon: "minus", classname: "indeterminate_icon hidden", fixed_width: true}) %>
6
+ </span>
14
7
  <span class="pb_checkbox_label">
15
8
  <%= pb_rails("body", props: { status: object.checkbox_label_status, text: object.text, dark: object.dark, margin_right: object.form_spacing ? "xs" : "" }) %>
16
9
  </span>
@@ -5,7 +5,8 @@ module Playbook
5
5
  class Checkbox < Playbook::KitBase
6
6
  prop :error, type: Playbook::Props::Boolean, default: false
7
7
  prop :checked, type: Playbook::Props::Boolean, default: false
8
- prop :indeterminate, type: Playbook::Props::Boolean, default: false
8
+ prop :indeterminate_main, type: Playbook::Props::Boolean, default: false
9
+ prop :indeterminate_parent
9
10
  prop :text
10
11
  prop :value
11
12
  prop :name
@@ -19,7 +20,7 @@ module Playbook
19
20
  default: false
20
21
 
21
22
  def classname
22
- generate_classname("pb_checkbox_kit", checked_class) + indeterminate_class + error_class
23
+ generate_classname("pb_checkbox_kit", checked_class) + error_class
23
24
  end
24
25
 
25
26
  def input
@@ -30,6 +31,13 @@ module Playbook
30
31
  error ? "negative" : nil
31
32
  end
32
33
 
34
+ def data
35
+ Hash(prop(:data)).merge(
36
+ pb_checkbox_indeterminate_main: indeterminate_main,
37
+ pb_checkbox_indeterminate_parent: indeterminate_parent
38
+ )
39
+ end
40
+
33
41
  private
34
42
 
35
43
  def error_class
@@ -39,10 +47,6 @@ module Playbook
39
47
  def checked_class
40
48
  checked ? "on" : "off"
41
49
  end
42
-
43
- def indeterminate_class
44
- indeterminate ? " indeterminate" : ""
45
- end
46
50
  end
47
51
  end
48
52
  end
@@ -9,11 +9,10 @@
9
9
  <tr>
10
10
  <th>
11
11
  <%= pb_rails("checkbox", props: {
12
- checked: true,
13
12
  text: "Uncheck All",
14
13
  value: "checkbox-value",
15
14
  name: "main-checkbox",
16
- indeterminate: true,
15
+ indeterminate_main: true,
17
16
  id: "indeterminate-checkbox"
18
17
  }) %>
19
18
  </th>
@@ -30,55 +29,10 @@
30
29
  value: checkbox[:id],
31
30
  name: "#{checkbox[:id]}-indeterminate-checkbox",
32
31
  id: "#{checkbox[:id]}-indeterminate-checkbox",
32
+ indeterminate_parent: "indeterminate-checkbox",
33
33
  }) %>
34
34
  </td>
35
35
  </tr>
36
36
  <% end %>
37
37
  </tbody>
38
38
  <% end %>
39
-
40
- <script>
41
- document.addEventListener('DOMContentLoaded', function() {
42
- const mainCheckboxWrapper = document.getElementById('indeterminate-checkbox');
43
- const mainCheckbox = document.getElementsByName("main-checkbox")[0];
44
- const childCheckboxes = document.querySelectorAll('input[type="checkbox"][id$="indeterminate-checkbox"]');
45
-
46
- const updateMainCheckbox = () => {
47
- // Count the number of checked child checkboxes
48
- const checkedCount = Array.from(childCheckboxes).filter(cb => cb.checked).length;
49
- // Determine if the main checkbox should be in an indeterminate state
50
- const indeterminate = checkedCount > 0 && checkedCount < childCheckboxes.length;
51
-
52
- // Set the main checkbox states
53
- mainCheckbox.indeterminate = indeterminate;
54
- mainCheckbox.checked = checkedCount > 0;
55
-
56
- // Determine the main checkbox label based on the number of checked checkboxes
57
- const text = checkedCount === 0 ? 'Check All' : 'Uncheck All';
58
-
59
- // Determine the icon class to add and remove based on the number of checked checkboxes
60
- const iconClassToAdd = checkedCount === 0 ? 'pb_checkbox_checkmark' : 'pb_checkbox_indeterminate';
61
- const iconClassToRemove = checkedCount === 0 ? 'pb_checkbox_indeterminate' : 'pb_checkbox_checkmark';
62
-
63
- // Update main checkbox label
64
- mainCheckboxWrapper.getElementsByClassName('pb_body_kit')[0].textContent = text;
65
-
66
- // Add and remove the icon class to the main checkbox wrapper
67
- mainCheckboxWrapper.querySelector('[data-pb-checkbox-icon-span]').classList.add(iconClassToAdd);
68
- mainCheckboxWrapper.querySelector('[data-pb-checkbox-icon-span]').classList.remove(iconClassToRemove);
69
-
70
- // Toggle the visibility of the checkbox icon based on the indeterminate state
71
- mainCheckboxWrapper.getElementsByClassName("indeterminate_icon")[0].classList.toggle('hidden', !indeterminate);
72
- mainCheckboxWrapper.getElementsByClassName("check_icon")[0].classList.toggle('hidden', indeterminate);
73
- };
74
-
75
- mainCheckbox.addEventListener('change', function() {
76
- childCheckboxes.forEach(cb => cb.checked = this.checked);
77
- updateMainCheckbox();
78
- });
79
-
80
- childCheckboxes.forEach(cb => {
81
- cb.addEventListener('change', updateMainCheckbox);
82
- });
83
- });
84
- </script>
@@ -0,0 +1 @@
1
+ If you want to use indeterminate, "check/uncheck all" checkboxes, add `indeterminate_main: true` and an `id` to the main checkbox. Then, add an `indeterminate_parent` prop with the main checkbox's `id` to the children checkboxes.
@@ -0,0 +1,56 @@
1
+ import PbEnhancedElement from "../pb_enhanced_element"
2
+
3
+ const INDETERMINATE_MAIN_CHECKBOX_SELECTOR = "[data-pb-checkbox-indeterminate-main='true']"
4
+
5
+ export default class PbCheckbox extends PbEnhancedElement {
6
+ static get selector() {
7
+ return INDETERMINATE_MAIN_CHECKBOX_SELECTOR
8
+ }
9
+
10
+ connect() {
11
+ const mainCheckboxWrapper = this.element;
12
+ const mainCheckbox = mainCheckboxWrapper.querySelector('input')
13
+ const childCheckboxes = document.querySelectorAll(`[data-pb-checkbox-indeterminate-parent="${this.element.id}"] input[type="checkbox"]`);
14
+
15
+ const updateMainCheckbox = () => {
16
+ // Count the number of checked child checkboxes
17
+ const checkedCount = Array.from(childCheckboxes).filter(cb => cb.checked).length;
18
+ // Determine if the main checkbox should be in an indeterminate state
19
+ const indeterminate = checkedCount > 0 && checkedCount < childCheckboxes.length;
20
+
21
+ // Set the main checkbox states
22
+ mainCheckbox.indeterminate = indeterminate;
23
+ mainCheckbox.checked = checkedCount > 0;
24
+
25
+ // Determine the main checkbox label based on the number of checked checkboxes
26
+ const text = checkedCount === 0 ? 'Check All' : 'Uncheck All';
27
+
28
+ // Determine the icon class to add and remove based on the number of checked checkboxes
29
+ const iconClassToAdd = checkedCount === 0 ? 'pb_checkbox_checkmark' : 'pb_checkbox_indeterminate';
30
+ const iconClassToRemove = checkedCount === 0 ? 'pb_checkbox_indeterminate' : 'pb_checkbox_checkmark';
31
+
32
+ // Update main checkbox label
33
+ mainCheckboxWrapper.getElementsByClassName('pb_body_kit')[0].textContent = text;
34
+
35
+ // Add and remove the icon class to the main checkbox wrapper
36
+ mainCheckboxWrapper.querySelector('[data-pb-checkbox-icon-span]').classList.add(iconClassToAdd);
37
+ mainCheckboxWrapper.querySelector('[data-pb-checkbox-icon-span]').classList.remove(iconClassToRemove);
38
+
39
+ // Toggle the visibility of the checkbox icon based on the indeterminate state
40
+ mainCheckboxWrapper.getElementsByClassName("indeterminate_icon")[0].classList.toggle('hidden', !indeterminate);
41
+ mainCheckboxWrapper.getElementsByClassName("check_icon")[0].classList.toggle('hidden', indeterminate);
42
+ };
43
+
44
+ // Set indeterminate icon on main checkbox if initial children checkboxes are checked
45
+ updateMainCheckbox();
46
+
47
+ this.element.querySelector('input').addEventListener('change', function() {
48
+ childCheckboxes.forEach(cb => cb.checked = this.checked);
49
+ updateMainCheckbox();
50
+ });
51
+
52
+ childCheckboxes.forEach(cb => {
53
+ cb.addEventListener('change', updateMainCheckbox);
54
+ });
55
+ }
56
+ }
@@ -80,14 +80,23 @@ const datePickerHelper = (config: DatePickerConfig, scrollContainer: string | HT
80
80
  // ===========================================================
81
81
  // | Hook Definitions |
82
82
  // ===========================================================
83
+ const pickerInput = document.querySelector<HTMLElement & { [x: string]: any }>(`#${pickerId}`)
84
+ const inTurboFrames = pickerInput?.closest('turbo-frame') !== null
83
85
 
84
86
  const defaultDateGetter = () => {
85
- if (defaultDate === '') {
86
- return null
87
+ if (inTurboFrames) {
88
+ const initialValue = pickerInput?.value?.trim();
89
+ if (initialValue === "") return null;
90
+ if (initialValue) return initialValue;
91
+ return defaultDate || null
87
92
  } else {
88
- return defaultDate
93
+ if (defaultDate === "") {
94
+ return null;
95
+ } else {
96
+ return defaultDate;
97
+ }
89
98
  }
90
- }
99
+ };
91
100
 
92
101
  const disabledWeekDays = () => {
93
102
  return (
@@ -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>
@@ -25,6 +25,7 @@ type DropdownProps = {
25
25
  blankSelection?: string;
26
26
  children?: React.ReactChild[] | React.ReactChild | React.ReactElement[];
27
27
  className?: string;
28
+ closeOnSelection?: boolean;
28
29
  formPillProps?: GenericObject;
29
30
  dark?: boolean;
30
31
  data?: { [key: string]: string };
@@ -55,6 +56,7 @@ let Dropdown = (props: DropdownProps, ref: any): React.ReactElement | null => {
55
56
  blankSelection = '',
56
57
  children,
57
58
  className,
59
+ closeOnSelection = true,
58
60
  dark = false,
59
61
  data = {},
60
62
  defaultValue = {},
@@ -152,7 +154,7 @@ let Dropdown = (props: DropdownProps, ref: any): React.ReactElement | null => {
152
154
  if (!multiSelect) return optionsWithBlankSelection;
153
155
  return optionsWithBlankSelection.filter((option: GenericObject) => !selectedArray.some((sel) => sel.label === option.label));
154
156
  }, [optionsWithBlankSelection, selectedArray, multiSelect]);
155
-
157
+
156
158
  const filteredOptions = useMemo(() => {
157
159
  return availableOptions.filter((opt: GenericObject) =>
158
160
  String(opt.label).toLowerCase().includes(filterItem.toLowerCase())
@@ -192,12 +194,18 @@ let Dropdown = (props: DropdownProps, ref: any): React.ReactElement | null => {
192
194
  return next;
193
195
  });
194
196
  setFilterItem("");
195
- setIsDropDownClosed(true);
197
+ // Only close dropdown if closeOnSelection is true
198
+ if (closeOnSelection) {
199
+ setIsDropDownClosed(true);
200
+ }
196
201
  } else {
197
202
  setSelected(clickedItem);
198
203
  setFilterItem("");
199
- setIsDropDownClosed(true);
200
204
  onSelect && onSelect(clickedItem);
205
+ // Only close dropdown if closeOnSelection is true
206
+ if (closeOnSelection) {
207
+ setIsDropDownClosed(true);
208
+ }
201
209
  }
202
210
  };
203
211
 
@@ -252,6 +260,7 @@ let Dropdown = (props: DropdownProps, ref: any): React.ReactElement | null => {
252
260
  <DropdownContext.Provider
253
261
  value={{
254
262
  autocomplete,
263
+ closeOnSelection,
255
264
  dropdownContainerRef,
256
265
  filteredOptions,
257
266
  filterItem,
@@ -0,0 +1,42 @@
1
+ import React from 'react'
2
+ import Dropdown from '../../pb_dropdown/_dropdown'
3
+
4
+ const DropdownCloseOnSelect = (props) => {
5
+
6
+ const options = [
7
+ {
8
+ label: "United States",
9
+ value: "United States",
10
+ },
11
+ {
12
+ label: "Canada",
13
+ value: "Canada",
14
+ },
15
+ {
16
+ label: "Pakistan",
17
+ value: "Pakistan",
18
+ }
19
+ ];
20
+
21
+
22
+ return (
23
+ <div>
24
+ <Dropdown
25
+ closeOnSelection={false}
26
+ label="Default"
27
+ options={options}
28
+ {...props}
29
+ />
30
+ <br />
31
+ <Dropdown
32
+ closeOnSelection={false}
33
+ label="Multi Select"
34
+ multiSelect
35
+ options={options}
36
+ {...props}
37
+ />
38
+ </div>
39
+ )
40
+ }
41
+
42
+ export default DropdownCloseOnSelect
@@ -0,0 +1 @@
1
+ By default, the dropdown menu will close when a selection is made. You can prevent this behavior by using the `closeOnSelection` prop, which will leave the menu open after a selection is made when set to 'false'.
@@ -44,4 +44,6 @@ examples:
44
44
  - dropdown_clear_selection: Clear Selection
45
45
  - dropdown_separators_hidden: Separators Hidden
46
46
  - dropdown_with_external_control: useDropdown Hook
47
+ - dropdown_close_on_select: Close On Selection
48
+
47
49
 
@@ -19,4 +19,5 @@ export { default as DropdownMultiSelect } from './_dropdown_multi_select.jsx'
19
19
  export { default as DropdownMultiSelectDisplay } from './_dropdown_multi_select_display.jsx'
20
20
  export { default as DropdownMultiSelectWithAutocomplete } from './_dropdown_multi_select_with_autocomplete.jsx'
21
21
  export { default as DropdownMultiSelectWithDefault } from './_dropdown_multi_select_with_default.jsx'
22
- export { default as DropdownMultiSelectWithCustomOptions } from './_dropdown_multi_select_with_custom_options.jsx'
22
+ export { default as DropdownMultiSelectWithCustomOptions } from './_dropdown_multi_select_with_custom_options.jsx'
23
+ export { default as DropdownCloseOnSelect } from './_dropdown_close_on_select.jsx'
@@ -25,7 +25,7 @@ type DropdownOptionProps = {
25
25
  key?: string | number;
26
26
  option?: GenericObject;
27
27
  padding?: string;
28
- } & GlobalProps;
28
+ } & GlobalProps;
29
29
 
30
30
  const DropdownOption = (props: DropdownOptionProps) => {
31
31
  const {
@@ -56,16 +56,17 @@ const DropdownOption = (props: DropdownOptionProps) => {
56
56
 
57
57
  // When multiSelect, then if an option is selected, remove from dropdown
58
58
  const isSelected = Array.isArray(selected)
59
- ? selected.some((item) => item.label === option?.label)
60
- : (selected as GenericObject)?.label === option?.label;
59
+ ? selected.some((item) => item.label === option?.label)
60
+ : (selected as GenericObject)?.label === option?.label;
61
61
 
62
-
63
62
  if (!isItemMatchingFilter(option) || (multiSelect && isSelected)) {
64
63
  return null;
65
64
  }
65
+
66
66
  const isFocused =
67
67
  focusedOptionIndex >= 0 &&
68
68
  filteredOptions[focusedOptionIndex].label === option?.label;
69
+
69
70
  const focusedClass = isFocused && "focused";
70
71
 
71
72
  const selectedClass = isSelected ? "selected" : "list";
@@ -91,7 +92,10 @@ const DropdownOption = (props: DropdownOptionProps) => {
91
92
  className={classes}
92
93
  id={id}
93
94
  key={key}
94
- onClick= {() => handleOptionClick(option)}
95
+ onClick={(e) => {
96
+ e.stopPropagation();
97
+ handleOptionClick(option);
98
+ }}
95
99
  >
96
100
  <ListItem
97
101
  cursor="pointer"
@@ -100,12 +104,12 @@ const DropdownOption = (props: DropdownOptionProps) => {
100
104
  key={option?.label}
101
105
  padding="none"
102
106
  >
103
- {children ?
107
+ {children ?
104
108
  <div className="dropdown_option_wrapper">{children}</div> :
105
- <Body dark={dark}
106
- text={option?.label}
107
- />
108
- }
109
+ <Body dark={dark}
110
+ text={option?.label}
111
+ />
112
+ }
109
113
  </ListItem>
110
114
  </div>
111
115
  );
@@ -44,6 +44,7 @@ const DropdownTrigger = (props: DropdownTriggerProps) => {
44
44
 
45
45
  const {
46
46
  autocomplete,
47
+ closeOnSelection,
47
48
  filterItem,
48
49
  handleBackspace,
49
50
  handleChange,
@@ -54,6 +55,7 @@ const DropdownTrigger = (props: DropdownTriggerProps) => {
54
55
  isInputFocused,
55
56
  multiSelect,
56
57
  selected,
58
+ setIsDropDownClosed,
57
59
  setIsInputFocused,
58
60
  toggleDropdown,
59
61
  } = useContext(DropdownContext);
@@ -103,11 +105,26 @@ const DropdownTrigger = (props: DropdownTriggerProps) => {
103
105
  ? placeholder
104
106
  : "Select...";
105
107
 
108
+ // Click handler that respects closeOnSelection
109
+ const handleInputClick = (e: React.MouseEvent) => {
110
+ e.stopPropagation(); // keep the wrapper's handler from firing
111
+ if (isDropDownClosed) {
112
+ // Always open if closed
113
+ setIsDropDownClosed(false);
114
+ } else if (!closeOnSelection) {
115
+ // Keep open if closeOnSelection is false
116
+ return;
117
+ } else {
118
+ // Default behavior - toggle
119
+ toggleDropdown();
120
+ }
121
+ };
122
+
106
123
  return (
107
- <div {...ariaProps}
108
- {...dataProps}
124
+ <div {...ariaProps}
125
+ {...dataProps}
109
126
  {...htmlProps}
110
- className={classes}
127
+ className={classes}
111
128
  id={id}
112
129
  >
113
130
  {
@@ -145,7 +162,7 @@ const DropdownTrigger = (props: DropdownTriggerProps) => {
145
162
  {customDisplay ? (
146
163
  <Flex align="center">
147
164
  {customDisplay}
148
- <Body dark={dark}
165
+ <Body dark={dark}
149
166
  paddingLeft={`${joinedLabels ? "xs" : "none"}`}
150
167
  >
151
168
  {customDisplayPlaceholder}
@@ -164,10 +181,7 @@ const DropdownTrigger = (props: DropdownTriggerProps) => {
164
181
  <input
165
182
  className="dropdown_input"
166
183
  onChange={handleChange}
167
- onClick={(e) => {
168
- e.stopPropagation();// keep the wrapper’s handler from firing
169
- toggleDropdown();
170
- }}
184
+ onClick={handleInputClick}
171
185
  onFocus={() => setIsInputFocused(true)}
172
186
  onKeyDown={(e) => {
173
187
  handleKeyDown(e);
@@ -186,8 +200,8 @@ const DropdownTrigger = (props: DropdownTriggerProps) => {
186
200
  )}
187
201
  </>
188
202
  ) : (
189
- <Body dark={dark}
190
- text={defaultDisplayPlaceholder}
203
+ <Body dark={dark}
204
+ text={defaultDisplayPlaceholder}
191
205
  />
192
206
  )
193
207
  )}
@@ -195,10 +209,7 @@ const DropdownTrigger = (props: DropdownTriggerProps) => {
195
209
  <input
196
210
  className="dropdown_input"
197
211
  onChange={handleChange}
198
- onClick={(e) => {
199
- e.stopPropagation();// keep the wrapper’s handler from firing
200
- toggleDropdown();
201
- }}
212
+ onClick={handleInputClick}
202
213
  onFocus={() => setIsInputFocused(true)}
203
214
  onKeyDown={handleKeyDown}
204
215
  placeholder={
@@ -223,7 +234,7 @@ const DropdownTrigger = (props: DropdownTriggerProps) => {
223
234
  onClick: (e: Event) => {e.stopPropagation();handleWrapperClick()}
224
235
  }}
225
236
  key={`${isDropDownClosed ? "chevron-down" : "chevron-up"}`}
226
- >
237
+ >
227
238
  {
228
239
  selectedArray.length > 0 && (
229
240
  <div onClick={(e)=>{e.stopPropagation();handleBackspace()}}>