playbook_ui 13.25.0 → 13.26.0.pre.alpha.PBNTR291Dropdownrailsv22840

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 (130) hide show
  1. checksums.yaml +4 -4
  2. data/app/pb_kits/playbook/index.js +1 -0
  3. data/app/pb_kits/playbook/pb_advanced_table/_advanced_table.scss +14 -0
  4. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_beta.html.erb +33 -0
  5. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_beta.md +24 -0
  6. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_default.md +5 -0
  7. data/app/pb_kits/playbook/pb_advanced_table/docs/example.yml +2 -2
  8. data/app/pb_kits/playbook/pb_advanced_table/index.js +78 -0
  9. data/app/pb_kits/playbook/pb_advanced_table/table_body.rb +4 -2
  10. data/app/pb_kits/playbook/pb_advanced_table/table_row.html.erb +3 -2
  11. data/app/pb_kits/playbook/pb_avatar/Utilities/GetPlacementPropsHelper.tsx +44 -0
  12. data/app/pb_kits/playbook/pb_avatar/_avatar.tsx +86 -21
  13. data/app/pb_kits/playbook/pb_avatar/avatar.html.erb +26 -3
  14. data/app/pb_kits/playbook/pb_avatar/avatar.rb +41 -0
  15. data/app/pb_kits/playbook/pb_avatar/docs/_avatar_badge_component_overlay.html.erb +71 -0
  16. data/app/pb_kits/playbook/pb_avatar/docs/_avatar_badge_component_overlay.jsx +77 -0
  17. data/app/pb_kits/playbook/pb_avatar/docs/_avatar_circle_icon_component_overlay.html.erb +71 -0
  18. data/app/pb_kits/playbook/pb_avatar/docs/_avatar_circle_icon_component_overlay.jsx +77 -0
  19. data/app/pb_kits/playbook/pb_avatar/docs/_avatar_default.jsx +20 -0
  20. data/app/pb_kits/playbook/pb_avatar/docs/example.yml +4 -0
  21. data/app/pb_kits/playbook/pb_avatar/docs/index.js +2 -0
  22. data/app/pb_kits/playbook/pb_bar_graph/_bar_graph.tsx +1 -1
  23. data/app/pb_kits/playbook/pb_body/_body.tsx +1 -1
  24. data/app/pb_kits/playbook/pb_button/_button.scss +1 -1
  25. data/app/pb_kits/playbook/pb_checkbox/_checkbox.scss +49 -0
  26. data/app/pb_kits/playbook/pb_checkbox/_checkbox.tsx +3 -0
  27. data/app/pb_kits/playbook/pb_checkbox/checkbox.rb +2 -1
  28. data/app/pb_kits/playbook/pb_checkbox/checkbox.test.js +14 -0
  29. data/app/pb_kits/playbook/pb_checkbox/docs/_checkbox_disabled.html.erb +23 -0
  30. data/app/pb_kits/playbook/pb_checkbox/docs/_checkbox_disabled.jsx +29 -0
  31. data/app/pb_kits/playbook/pb_checkbox/docs/example.yml +2 -0
  32. data/app/pb_kits/playbook/pb_checkbox/docs/index.js +1 -0
  33. data/app/pb_kits/playbook/pb_currency/docs/_currency_alignment_swift.md +43 -0
  34. data/app/pb_kits/playbook/pb_currency/docs/_currency_props_swift.md +12 -0
  35. data/app/pb_kits/playbook/pb_currency/docs/_currency_size_swift.md +31 -0
  36. data/app/pb_kits/playbook/pb_currency/docs/example.yml +5 -0
  37. data/app/pb_kits/playbook/pb_date_picker/docs/_date_picker_on_change.md +3 -1
  38. data/app/pb_kits/playbook/pb_date_picker/docs/_date_picker_on_close.md +3 -1
  39. data/app/pb_kits/playbook/pb_date_picker/docs/_date_picker_quick_pick_range_limit.md +1 -1
  40. data/app/pb_kits/playbook/pb_date_range_stacked/docs/_date_range_stacked_default_swift.md +14 -0
  41. data/app/pb_kits/playbook/pb_date_range_stacked/docs/_date_range_stacked_props_swift.md +9 -0
  42. data/app/pb_kits/playbook/pb_date_range_stacked/docs/example.yml +4 -0
  43. data/app/pb_kits/playbook/pb_dialog/_dialog.scss +4 -2
  44. data/app/pb_kits/playbook/pb_dropdown/_dropdown.scss +102 -35
  45. data/app/pb_kits/playbook/pb_dropdown/_dropdown.tsx +95 -26
  46. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_default.html.erb +10 -0
  47. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_default.jsx +4 -22
  48. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_default.md +1 -0
  49. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_subcomponent_structure.html.erb +17 -0
  50. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_subcomponent_structure.jsx +42 -0
  51. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_subcomponent_structure.md +7 -0
  52. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_autocomplete.jsx +84 -0
  53. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_autocomplete.md +1 -0
  54. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_autocomplete_and_custom_display.jsx +101 -0
  55. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_autocomplete_and_custom_display.md +1 -0
  56. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_custom_display.html.erb +60 -0
  57. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_custom_display.jsx +6 -4
  58. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_custom_display.md +5 -0
  59. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_custom_options.html.erb +45 -0
  60. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_custom_options.jsx +6 -9
  61. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_custom_options.md +1 -0
  62. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_custom_padding.html.erb +17 -0
  63. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_custom_padding.jsx +48 -0
  64. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_custom_padding.md +1 -0
  65. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_custom_trigger.html.erb +47 -0
  66. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_custom_trigger.jsx +5 -5
  67. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_custom_trigger.md +1 -0
  68. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_external_control.jsx +59 -0
  69. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_hook.jsx +72 -0
  70. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_label.html.erb +10 -0
  71. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_label.jsx +39 -0
  72. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_label.md +1 -0
  73. data/app/pb_kits/playbook/pb_dropdown/docs/example.yml +16 -2
  74. data/app/pb_kits/playbook/pb_dropdown/docs/index.js +7 -0
  75. data/app/pb_kits/playbook/pb_dropdown/dropdown.html.erb +26 -0
  76. data/app/pb_kits/playbook/pb_dropdown/dropdown.rb +20 -0
  77. data/app/pb_kits/playbook/pb_dropdown/dropdown.test.jsx +200 -10
  78. data/app/pb_kits/playbook/pb_dropdown/dropdown_container.html.erb +21 -0
  79. data/app/pb_kits/playbook/pb_dropdown/dropdown_container.rb +19 -0
  80. data/app/pb_kits/playbook/pb_dropdown/dropdown_option.html.erb +27 -0
  81. data/app/pb_kits/playbook/pb_dropdown/dropdown_option.rb +22 -0
  82. data/app/pb_kits/playbook/pb_dropdown/dropdown_trigger.html.erb +43 -0
  83. data/app/pb_kits/playbook/pb_dropdown/dropdown_trigger.rb +30 -0
  84. data/app/pb_kits/playbook/pb_dropdown/hooks/useDropdown.tsx +2 -2
  85. data/app/pb_kits/playbook/pb_dropdown/hooks/useHandleOnKeydown.tsx +14 -9
  86. data/app/pb_kits/playbook/pb_dropdown/index.js +153 -0
  87. data/app/pb_kits/playbook/pb_dropdown/keyboard_accessibility.js +77 -0
  88. data/app/pb_kits/playbook/pb_dropdown/scss_partials/_dropdown_animation.scss +18 -0
  89. data/app/pb_kits/playbook/pb_dropdown/subcomponents/DropdownContainer.tsx +22 -8
  90. data/app/pb_kits/playbook/pb_dropdown/subcomponents/DropdownOption.tsx +60 -31
  91. data/app/pb_kits/playbook/pb_dropdown/subcomponents/DropdownTrigger.tsx +130 -68
  92. data/app/pb_kits/playbook/pb_dropdown/utilities/clickOutsideHelper.tsx +41 -0
  93. data/app/pb_kits/playbook/pb_dropdown/utilities/index.ts +2 -0
  94. data/app/pb_kits/playbook/pb_dropdown/utilities/subComponentHelper.tsx +9 -7
  95. data/app/pb_kits/playbook/pb_loading_inline/_loading_inline.tsx +3 -1
  96. data/app/pb_kits/playbook/pb_loading_inline/docs/_loading_inline_custom.html.erb +13 -0
  97. data/app/pb_kits/playbook/pb_loading_inline/docs/_loading_inline_custom.jsx +26 -0
  98. data/app/pb_kits/playbook/pb_loading_inline/docs/{_loading_inline_light.html.erb → _loading_inline_default.html.erb} +2 -2
  99. data/app/pb_kits/playbook/pb_loading_inline/docs/{_loading_inline_light.jsx → _loading_inline_default.jsx} +2 -2
  100. data/app/pb_kits/playbook/pb_loading_inline/docs/example.yml +4 -2
  101. data/app/pb_kits/playbook/pb_loading_inline/docs/index.js +2 -1
  102. data/app/pb_kits/playbook/pb_loading_inline/loading_inline.html.erb +1 -1
  103. data/app/pb_kits/playbook/pb_loading_inline/loading_inline.rb +1 -0
  104. data/app/pb_kits/playbook/pb_loading_inline/loading_inline.test.js +14 -0
  105. data/app/pb_kits/playbook/pb_progress_simple/docs/_progress_simple_flex.html.erb +3 -0
  106. data/app/pb_kits/playbook/pb_progress_simple/docs/_progress_simple_flex.jsx +16 -0
  107. data/app/pb_kits/playbook/pb_progress_simple/docs/_progress_simple_flex.md +1 -0
  108. data/app/pb_kits/playbook/pb_progress_simple/docs/example.yml +2 -0
  109. data/app/pb_kits/playbook/pb_progress_simple/docs/index.js +1 -0
  110. data/app/pb_kits/playbook/pb_progress_simple/progress_simple.rb +1 -1
  111. data/app/pb_kits/playbook/pb_radio/_radio.scss +35 -0
  112. data/app/pb_kits/playbook/pb_radio/_radio.tsx +3 -0
  113. data/app/pb_kits/playbook/pb_radio/docs/_radio_alignment.jsx +4 -1
  114. data/app/pb_kits/playbook/pb_radio/docs/_radio_default.jsx +4 -1
  115. data/app/pb_kits/playbook/pb_radio/docs/_radio_disabled.html.erb +26 -0
  116. data/app/pb_kits/playbook/pb_radio/docs/_radio_disabled.jsx +31 -0
  117. data/app/pb_kits/playbook/pb_radio/docs/_radio_error.jsx +2 -1
  118. data/app/pb_kits/playbook/pb_radio/docs/example.yml +2 -0
  119. data/app/pb_kits/playbook/pb_radio/docs/index.js +1 -0
  120. data/app/pb_kits/playbook/pb_radio/radio.rb +5 -0
  121. data/app/pb_kits/playbook/pb_radio/radio.test.js +17 -0
  122. data/app/pb_kits/playbook/pb_section_separator/_section_separator.scss +6 -2
  123. data/app/pb_kits/playbook/pb_section_separator/_section_separator_mixin.scss +11 -1
  124. data/app/pb_kits/playbook/pb_tooltip/_tooltip.tsx +1 -1
  125. data/app/pb_kits/playbook/playbook-rails.js +6 -0
  126. data/dist/menu.yml +1 -1
  127. data/dist/playbook-rails.js +6 -6
  128. data/lib/playbook/version.rb +2 -2
  129. metadata +65 -8
  130. data/app/pb_kits/playbook/pb_advanced_table/docs/_description.md +0 -1
@@ -5,46 +5,51 @@
5
5
  @import "../tokens/shadows";
6
6
  @import "../tokens/positioning";
7
7
  @import "../pb_body/body_mixins";
8
+ @import "../pb_textarea/textarea_mixin";
9
+
10
+ @import "./scss_partials/dropdown_animation";
8
11
 
9
12
  .pb_dropdown {
10
13
  .dropdown_wrapper {
11
- position: relative;
12
- .dropdown_trigger_wrapper {
14
+ [class*="dropdown_trigger_wrapper"] {
13
15
  @include pb_body;
14
16
  border: 1px solid $border_light;
15
17
  background-color: $white;
16
- box-shadow: inset 0 -11px 20px rgba($primary, 0.05);
18
+ height: 45px;
17
19
  @media (hover: hover) {
18
20
  &:hover,
19
21
  &:active,
20
22
  &:focus {
21
23
  background-color: $focus_input_light;
22
- }
23
- input {
24
- background-color: $focus_input_light;
24
+ input {
25
+ background-color: $focus_input_light;
26
+ }
25
27
  }
26
28
  }
27
29
 
28
30
  .dropdown_input {
29
31
  @include pb_body;
30
32
  border: unset;
31
- border-radius: $border_rad_heavier;
32
33
  padding: 0;
33
-
34
+ background-color: $white;
34
35
  &:focus-visible {
35
36
  outline: none;
36
37
  }
37
38
  }
38
39
  &:focus {
39
- box-shadow: 0px 0px 0 1px $primary;
40
+ box-shadow: 0px 0px 0 1px $primary !important;
40
41
  outline: unset;
41
42
  transition: box-shadow 0.15s ease-in-out;
42
43
  }
43
- }
44
44
 
45
- .dropdown_trigger_wrapper_focus {
46
- box-shadow: 0px 0px 0 1px $primary;
47
- transition: box-shadow .10s ease-in-out;
45
+ &[class*="_select_only"] {
46
+ box-shadow: inset 0 -11px 20px rgba($primary, 0.05);
47
+ }
48
+
49
+ &[class*="_focus"] {
50
+ box-shadow: 0px 0px 0 1px $primary !important;
51
+ transition: box-shadow 0.1s ease-in-out;
52
+ }
48
53
  }
49
54
 
50
55
  .pb_dropdown_container {
@@ -54,45 +59,107 @@
54
59
  border-radius: $border_rad_heavier;
55
60
  border: 1px solid $border_light;
56
61
  margin-top: $space_xs;
57
- position: absolute;
58
62
  z-index: $z_1;
59
63
  width: 100%;
60
- transition: opacity 0.25s ease-in-out;
61
64
 
62
- .pb_dropdown_option {
63
- :hover {
65
+ [class*="pb_dropdown_option"] {
66
+ cursor: pointer;
67
+ &:hover {
64
68
  background-color: $border_light;
65
69
  }
66
- }
67
-
68
- .dropdown_option_focused {
69
- background-color: $border_light;
70
- }
71
70
 
72
- .dropdown_option {
73
- width: 100%;
74
- }
71
+ &[class*="_focused"] {
72
+ background-color: $border_light;
73
+ }
75
74
 
76
- .dropdown_option_list {
77
- border-bottom: 1px solid $border_light;
78
- }
79
- .dropdown_option_selected {
80
- background-color: $primary;
81
- [class^="pb_body"],
82
- [class^="pb_title_kit"] {
83
- color: $white !important;
75
+ &[class*="_list"] {
76
+ border-bottom: 1px solid $border_light;
84
77
  }
85
- :hover {
86
- background-color: unset;
78
+ &[class*="selected"] {
79
+ background-color: $primary;
80
+ color: $white;
81
+ [class^="pb_body"],
82
+ [class^="pb_title_kit"], a {
83
+ color: $white !important;
84
+ }
85
+ &:hover {
86
+ background-color: $primary !important;
87
+ }
87
88
  }
88
89
  }
90
+
91
+ .dropdown_option_wrapper {
92
+ width: 100%;
93
+ }
89
94
  }
90
95
  .close {
91
96
  display: none;
97
+ animation-name: fadeOut;
98
+ animation-duration: 150ms;
99
+ animation-timing-function: linear;
100
+ animation-fill-mode: forwards;
92
101
  }
93
102
 
94
103
  .open {
95
104
  display: block;
105
+ animation-name: fadeIn;
106
+ animation-duration: 150ms;
107
+ animation-timing-function: linear;
108
+ animation-fill-mode: forwards;
109
+ }
110
+ }
111
+
112
+ &.dark {
113
+ .dropdown_wrapper {
114
+ [class*="dropdown_trigger_wrapper"] {
115
+ @include pb_body_light_dark;
116
+ background-color: rgba($white, 0.1) !important;
117
+ background: none;
118
+ border-color: rgba($white, 0.15);
119
+ @media (hover: hover) {
120
+ &:hover,
121
+ &:active,
122
+ &:focus {
123
+ background-color: rgba($white, 0.05) !important;
124
+ }
125
+ }
126
+ [class^="pb_body"] {
127
+ color: $white;
128
+ }
129
+ &[class*="_select_only"] {
130
+ box-shadow: inset 0 -11px 20px rgba($white, 0.05);
131
+ }
132
+ .dropdown_input {
133
+ background-color: unset;
134
+ color: $white;
135
+ }
136
+ }
137
+ }
138
+ .pb_dropdown_container {
139
+ background-color: $bg_dark !important;
140
+ border-color: rgba($white, 0.15);
141
+ color: $white;
142
+ [class^="pb_body"],
143
+ [class^="pb_title_kit"] {
144
+ color: $white !important;
145
+ }
146
+
147
+ [class*="pb_dropdown_option"] {
148
+ &:hover {
149
+ background-color: $hover_dark;
150
+ }
151
+
152
+ &[class*="_focused"] {
153
+ background-color: $hover_dark;
154
+ }
155
+
156
+ &[class*="_list"] {
157
+ border-color: rgba($white, 0.15);
158
+ }
159
+ &[class*="selected"] {
160
+ background-color: $primary;
161
+ }
162
+ }
96
163
  }
97
164
  }
98
165
  }
@@ -1,31 +1,38 @@
1
- import React, { useState, useRef, useEffect, ReactElement } from "react";
1
+ import React, { useState, useRef, useEffect } from "react";
2
2
  import classnames from "classnames";
3
- import { buildAriaProps, buildCss, buildDataProps } from "../utilities/props";
3
+ import { buildAriaProps, buildCss, buildDataProps, buildHtmlProps } from "../utilities/props";
4
4
  import { globalProps } from "../utilities/globalProps";
5
+ import { GenericObject } from "../types";
5
6
 
6
7
  import Body from "../pb_body/_body";
8
+ import Caption from "../pb_caption/_caption";
7
9
 
8
10
  import DropdownContainer from "./subcomponents/DropdownContainer";
11
+ import DropdownContext from "./context";
9
12
  import DropdownOption from "./subcomponents/DropdownOption";
10
13
  import DropdownTrigger from "./subcomponents/DropdownTrigger";
11
- import DropdownContext from "./context";
12
14
  import useDropdown from "./hooks/useDropdown";
13
15
 
14
16
  import {
15
17
  separateChildComponents,
16
18
  prepareSubcomponents,
17
- } from "./utilities/subComponentHelper";
18
- import { GenericObject } from "../types";
19
+ handleClickOutside,
20
+ } from "./utilities";
19
21
 
20
22
  type DropdownProps = {
21
23
  aria?: { [key: string]: string };
22
24
  autocomplete?: boolean;
25
+ children?: React.ReactChild[] | React.ReactChild | React.ReactElement[];
23
26
  className?: string;
27
+ dark?: boolean;
24
28
  data?: { [key: string]: string };
29
+ htmlOptions?: {[key: string]: string | number | boolean | (() => void)},
25
30
  id?: string;
26
- children?: React.ReactChild[] | React.ReactChild | ReactElement[];
27
- options: GenericObject;
31
+ isClosed?: boolean;
32
+ label?: string;
28
33
  onSelect?: (arg: GenericObject) => null;
34
+ options: GenericObject;
35
+ triggerRef?: any;
29
36
  };
30
37
 
31
38
  const Dropdown = (props: DropdownProps) => {
@@ -34,49 +41,66 @@ const Dropdown = (props: DropdownProps) => {
34
41
  autocomplete = false,
35
42
  children,
36
43
  className,
44
+ dark = false,
37
45
  data = {},
46
+ htmlOptions = {},
38
47
  id,
39
- options,
48
+ isClosed = true,
49
+ label,
40
50
  onSelect,
51
+ options,
52
+ triggerRef
41
53
  } = props;
42
54
 
43
55
  const ariaProps = buildAriaProps(aria);
44
56
  const dataProps = buildDataProps(data);
57
+ const htmlProps = buildHtmlProps(htmlOptions);
45
58
  const classes = classnames(
46
59
  buildCss("pb_dropdown"),
47
60
  globalProps(props),
48
61
  className
49
62
  );
50
63
 
51
- const [isDropDownClosed, setIsDropDownClosed, toggleDropdown] = useDropdown();
64
+ const [isDropDownClosed, setIsDropDownClosed, toggleDropdown] = useDropdown(isClosed);
52
65
 
53
66
  const [filterItem, setFilterItem] = useState("");
54
- const [selected, setSelected] = useState({});
67
+ const [selected, setSelected] = useState<GenericObject>({});
55
68
  const [isInputFocused, setIsInputFocused] = useState(false);
56
69
  const [hasTriggerSubcomponent, setHasTriggerSubcomponent] = useState(true);
57
70
  const [hasContainerSubcomponent, setHasContainerSubcomponent] =
58
71
  useState(true);
59
-
60
72
  //state for keyboard events
61
73
  const [focusedOptionIndex, setFocusedOptionIndex] = useState(-1);
62
74
 
63
75
  const dropdownRef = useRef(null);
64
76
  const inputRef = useRef(null);
77
+ const inputWrapperRef = useRef(null);
78
+ const dropdownContainerRef = useRef(null);
65
79
 
66
80
  const { trigger, container, otherChildren } =
67
81
  separateChildComponents(children);
68
82
 
69
- // useEffect to handle clicks outside the dropdown
70
83
  useEffect(() => {
71
- const handleClickOutside = (e: MouseEvent) => {
72
- if (dropdownRef.current && !dropdownRef.current.contains(e.target)) {
73
- setIsDropDownClosed(true);
74
- setIsInputFocused(false);
84
+ // Set the parent element of the trigger to relative to allow for absolute positioning of the dropdown
85
+ //Only needed for when useDropdown hook used with external trigger
86
+ if (triggerRef?.current) {
87
+ const parentElement = triggerRef.current.parentNode;
88
+ if (parentElement) {
89
+ parentElement.style.position = 'relative';
75
90
  }
76
- };
77
- window.addEventListener("click", handleClickOutside);
91
+ }
92
+ // Handle clicks outside the dropdown
93
+ const handleClick = handleClickOutside({
94
+ inputWrapperRef,
95
+ dropdownContainerRef,
96
+ setIsDropDownClosed,
97
+ setFocusedOptionIndex,
98
+ setIsInputFocused,
99
+ });
100
+
101
+ window.addEventListener("click", handleClick);
78
102
  return () => {
79
- window.removeEventListener("click", handleClickOutside);
103
+ window.removeEventListener("click", handleClick);
80
104
  };
81
105
  }, []);
82
106
 
@@ -85,6 +109,32 @@ const Dropdown = (props: DropdownProps) => {
85
109
  setHasContainerSubcomponent(!!container);
86
110
  }, []);
87
111
 
112
+ // dropdown to toggle with external control
113
+ useEffect(()=> {
114
+ setIsDropDownClosed(isClosed)
115
+ },[isClosed])
116
+
117
+ const filteredOptions = options?.filter((option: GenericObject) => {
118
+ const label = typeof option.label === 'string' ? option.label.toLowerCase() : option.label;
119
+ return String(label).toLowerCase().includes(filterItem.toLowerCase());
120
+ }
121
+ );
122
+
123
+ // For keyboard accessibility: Set focus within dropdown to selected item if it exists
124
+ useEffect(() => {
125
+ if (!isDropDownClosed) {
126
+ let newIndex = 0;
127
+ if (selected && selected?.label) {
128
+ const selectedIndex = filteredOptions.findIndex((option: GenericObject) => option.label === selected.label);
129
+ if (selectedIndex >= 0) {
130
+ newIndex = selectedIndex;
131
+ }
132
+ }
133
+ setFocusedOptionIndex(newIndex);
134
+ }
135
+ }, [isDropDownClosed]);
136
+
137
+
88
138
  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
89
139
  setFilterItem(e.target.value);
90
140
  setIsDropDownClosed(false);
@@ -94,7 +144,7 @@ const Dropdown = (props: DropdownProps) => {
94
144
  setSelected(selectedItem);
95
145
  setFilterItem("");
96
146
  setIsDropDownClosed(true);
97
- onSelect(selectedItem);
147
+ onSelect && onSelect(selectedItem);
98
148
  };
99
149
 
100
150
  const handleWrapperClick = () => {
@@ -104,14 +154,10 @@ const Dropdown = (props: DropdownProps) => {
104
154
 
105
155
  const handleBackspace = () => {
106
156
  setSelected({});
107
- onSelect(null);
157
+ onSelect && onSelect(null);
108
158
  setFocusedOptionIndex(-1);
109
159
  };
110
160
 
111
- const filteredOptions = options?.filter((option: GenericObject) =>
112
- option.label.toLowerCase().includes(filterItem.toLowerCase())
113
- );
114
-
115
161
  const componentsToRender = prepareSubcomponents({
116
162
  children,
117
163
  hasTriggerSubcomponent,
@@ -119,17 +165,22 @@ const Dropdown = (props: DropdownProps) => {
119
165
  trigger,
120
166
  container,
121
167
  otherChildren,
168
+ dark
122
169
  });
123
170
 
171
+
124
172
  return (
125
173
  <div {...ariaProps}
126
174
  {...dataProps}
175
+ {...htmlProps}
127
176
  className={classes}
128
177
  id={id}
178
+ style={triggerRef ? { position: "absolute"} : { position: "relative"}}
129
179
  >
130
180
  <DropdownContext.Provider
131
181
  value={{
132
182
  autocomplete,
183
+ dropdownContainerRef,
133
184
  filteredOptions,
134
185
  filterItem,
135
186
  focusedOptionIndex,
@@ -138,6 +189,7 @@ const Dropdown = (props: DropdownProps) => {
138
189
  handleOptionClick,
139
190
  handleWrapperClick,
140
191
  inputRef,
192
+ inputWrapperRef,
141
193
  isDropDownClosed,
142
194
  isInputFocused,
143
195
  options,
@@ -147,9 +199,26 @@ const Dropdown = (props: DropdownProps) => {
147
199
  setIsInputFocused,
148
200
  setSelected,
149
201
  toggleDropdown,
202
+ triggerRef
150
203
  }}
151
204
  >
205
+ {label &&
206
+ <Caption
207
+ dark={dark}
208
+ marginBottom="xs"
209
+ text={label}
210
+ />
211
+ }
152
212
  <div className="dropdown_wrapper"
213
+ onBlur={() => {
214
+ // Debounce to delay the execution to prevent jumpiness in Focus state
215
+ setTimeout(() => {
216
+ if (!dropdownRef.current.contains(document.activeElement)) {
217
+ setIsInputFocused(false);
218
+ }
219
+ }, 0);
220
+ }}
221
+ onFocus={() => setIsInputFocused(true)}
153
222
  ref={dropdownRef}
154
223
  >
155
224
  {children ? (
@@ -176,7 +245,7 @@ const Dropdown = (props: DropdownProps) => {
176
245
  </div>
177
246
  </DropdownContext.Provider>
178
247
  </div>
179
- );
248
+ )
180
249
  };
181
250
 
182
251
  Dropdown.Option = DropdownOption;
@@ -0,0 +1,10 @@
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
+ <%= pb_rails("dropdown", props: {options: options}) %>
@@ -1,31 +1,20 @@
1
- import React, { useState } from 'react'
1
+ import React from 'react'
2
2
  import { Dropdown } from '../../'
3
3
 
4
4
  const DropdownDefault = (props) => {
5
- // eslint-disable-next-line no-unused-vars
6
- const [selectedOption, setSelectedOption] = useState();
7
5
 
8
6
  const options = [
9
7
  {
10
8
  label: "United States",
11
9
  value: "United States",
12
- areaCode: "+1",
13
- icon: "🇺🇸",
14
- id: "United-states"
15
10
  },
16
11
  {
17
- label: "Ukraine",
18
- value: "Ukraine",
19
- areaCode: "+380",
20
- icon: "🇺🇦",
21
- id: "ukraine"
12
+ label: "Canada",
13
+ value: "Canada",
22
14
  },
23
15
  {
24
16
  label: "Pakistan",
25
17
  value: "Pakistan",
26
- areaCode: "+92",
27
- icon: "🇵🇰",
28
- id: "pakistan"
29
18
  }
30
19
  ];
31
20
 
@@ -33,16 +22,9 @@ const [selectedOption, setSelectedOption] = useState();
33
22
  return (
34
23
  <div>
35
24
  <Dropdown
36
- onSelect={(selectedItem) => setSelectedOption(selectedItem)}
37
25
  options={options}
38
26
  {...props}
39
- >
40
- {options.map((option) => (
41
- <Dropdown.Option key={option.id}
42
- option={option}
43
- />
44
- ))}
45
- </Dropdown>
27
+ />
46
28
  </div>
47
29
  )
48
30
  }
@@ -0,0 +1 @@
1
+ The Dropdown kit accepts an `options` array and renders each object from that array as a selectable option within a dropdown container. `options` is a required prop and must be an array of objects. Each object can contain as many key/value pairs as needed but MUST contain 'label' and 'value' as the only required items within each object.
@@ -0,0 +1,17 @@
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
+ <%= pb_rails("dropdown", props: {options: options}) do %>
11
+ <%= pb_rails("dropdown/dropdown_trigger") %>
12
+ <%= pb_rails("dropdown/dropdown_container") do %>
13
+ <% options.each do |option| %>
14
+ <%= pb_rails("dropdown/dropdown_option", props: {option: option}) %>
15
+ <% end %>
16
+ <% end %>
17
+ <% end %>
@@ -0,0 +1,42 @@
1
+ import React from 'react'
2
+ import { Dropdown } from '../..'
3
+
4
+ const DropdownSubcomponentStructure = (props) => {
5
+
6
+
7
+ const options = [
8
+ {
9
+ label: "United States",
10
+ value: "United States",
11
+ },
12
+ {
13
+ label: "Canada",
14
+ value: "Canada",
15
+ },
16
+ {
17
+ label: "Pakistan",
18
+ value: "Pakistan",
19
+ }
20
+ ];
21
+
22
+
23
+ return (
24
+ <div>
25
+ <Dropdown
26
+ options={options}
27
+ {...props}
28
+ >
29
+ <Dropdown.Trigger/>
30
+ <Dropdown.Container>
31
+ {options.map((option) => (
32
+ <Dropdown.Option key={option.id}
33
+ option={option}
34
+ />
35
+ ))}
36
+ </Dropdown.Container>
37
+ </Dropdown>
38
+ </div>
39
+ )
40
+ }
41
+
42
+ export default DropdownSubcomponentStructure
@@ -0,0 +1,7 @@
1
+ The dropdown comes with the following subcomponents that can be used to achieve various levels of customization:
2
+
3
+ `Dropdown. Trigger` / `dropdown/dropdown_trigger`
4
+ `Dropdown.Container`/ `dropdown/dropdown_container`
5
+ `Dropdown.Option` / `dropdown/dropdown_option`
6
+
7
+ See the code snippet below for a visual on how to use the kit with subcomponents. Each subcomponent allows for GlobalProps in addition to any subcomponent specfic props.
@@ -0,0 +1,84 @@
1
+ import React from 'react'
2
+ import { Dropdown, User, Badge, FlexItem } from '../..'
3
+
4
+ const DropdownWithAutocomplete = (props) => {
5
+
6
+ const options = [
7
+ {
8
+ label: "Jasper Furniss",
9
+ value: "Jasper Furniss",
10
+ territory: "PHL",
11
+ title: "Senior UX Engineer",
12
+ id: "jasper-furniss",
13
+ status: "Offline"
14
+ },
15
+ {
16
+ label: "Ramon Ruiz",
17
+ value: "Ramon Ruiz",
18
+ territory: "PHL",
19
+ title: "Senior UX Designer",
20
+ id: "ramon-ruiz",
21
+ status: "Away"
22
+ },
23
+ {
24
+ label: "Jason Cypret",
25
+ value: "Jason Cypret",
26
+ territory: "PHL",
27
+ title: "VP of User Experience",
28
+ id: "jason-cypret",
29
+ status: "Online"
30
+ },
31
+ {
32
+ label: "Courtney Long",
33
+ value: "Courtney Long",
34
+ territory: "PHL",
35
+ title: "UX Design Mentor",
36
+ id: "courtney-long",
37
+ status: "Online"
38
+ }
39
+ ];
40
+
41
+
42
+ return (
43
+ <div>
44
+ <Dropdown autocomplete
45
+ options={options}
46
+ {...props}
47
+ >
48
+ {options.map((option) => (
49
+ <Dropdown.Option key={option.id}
50
+ option={option}
51
+ >
52
+ <>
53
+ <FlexItem>
54
+ <User
55
+ align="left"
56
+ avatar
57
+ name={option.label}
58
+ orientation="horizontal"
59
+ territory={option.territory}
60
+ title={option.title}
61
+ />
62
+ </FlexItem>
63
+ <FlexItem>
64
+ <Badge
65
+ rounded
66
+ text={option.status}
67
+ variant={`${
68
+ option.status === "Offline"
69
+ ? "neutral"
70
+ : option.status === "Online"
71
+ ? "success"
72
+ : "warning"
73
+ }`}
74
+ />
75
+ </FlexItem>
76
+ </>
77
+ </Dropdown.Option>
78
+ ))}
79
+ </Dropdown>
80
+ </div>
81
+ )
82
+ }
83
+
84
+ export default DropdownWithAutocomplete
@@ -0,0 +1 @@
1
+ The `autocomplete` prop can be used to add autocomplete or typeahead functionality to the Dropdown's default Trigger. This prop is set to 'false' by default.