playbook_ui 13.25.0 → 13.26.0.pre.alpha.PBNTR291Dropdownrailsv22840

Sign up to get free protection for your applications and to get access to all the features.
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.