playbook_ui 13.24.0 β 13.25.0.pre.alpha.PBNTR272Dropdownkitv42769
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.
- checksums.yaml +4 -4
- data/app/pb_kits/playbook/_playbook.scss +2 -0
- data/app/pb_kits/playbook/index.js +2 -0
- data/app/pb_kits/playbook/pb_advanced_table/advanced_table.html.erb +10 -14
- data/app/pb_kits/playbook/pb_advanced_table/table_body.html.erb +5 -9
- data/app/pb_kits/playbook/pb_advanced_table/table_header.html.erb +2 -6
- data/app/pb_kits/playbook/pb_advanced_table/table_row.html.erb +2 -6
- data/app/pb_kits/playbook/pb_avatar/avatar.html.erb +1 -6
- data/app/pb_kits/playbook/pb_avatar_action_button/avatar_action_button.html.erb +1 -6
- data/app/pb_kits/playbook/pb_background/_background.tsx +7 -5
- data/app/pb_kits/playbook/pb_background/background.html.erb +2 -11
- data/app/pb_kits/playbook/pb_badge/badge.html.erb +1 -6
- data/app/pb_kits/playbook/pb_bar_graph/_bar_graph.tsx +41 -6
- data/app/pb_kits/playbook/pb_bar_graph/bar_graph.rb +4 -0
- data/app/pb_kits/playbook/pb_bar_graph/docs/_bar_graph_negative_numbers.html.erb +23 -0
- data/app/pb_kits/playbook/pb_bar_graph/docs/_bar_graph_negative_numbers.jsx +35 -0
- data/app/pb_kits/playbook/pb_bar_graph/docs/_bar_graph_secondary_y_axis.html.erb +26 -0
- data/app/pb_kits/playbook/pb_bar_graph/docs/_bar_graph_secondary_y_axis.jsx +36 -0
- data/app/pb_kits/playbook/pb_bar_graph/docs/_bar_graph_secondary_y_axis.md +3 -0
- data/app/pb_kits/playbook/pb_bar_graph/docs/_bar_graph_stacked.html.erb +22 -0
- data/app/pb_kits/playbook/pb_bar_graph/docs/_bar_graph_stacked.jsx +34 -0
- data/app/pb_kits/playbook/pb_bar_graph/docs/_bar_graph_stacked.md +1 -0
- data/app/pb_kits/playbook/pb_bar_graph/docs/example.yml +6 -0
- data/app/pb_kits/playbook/pb_bar_graph/docs/index.js +3 -0
- data/app/pb_kits/playbook/pb_body/_body.scss +3 -3
- data/app/pb_kits/playbook/pb_body/_body.tsx +1 -1
- data/app/pb_kits/playbook/pb_body/body.html.erb +1 -6
- data/app/pb_kits/playbook/pb_bread_crumbs/bread_crumb_item.html.erb +1 -6
- data/app/pb_kits/playbook/pb_bread_crumbs/bread_crumbs.html.erb +2 -7
- data/app/pb_kits/playbook/pb_button/_button.scss +1 -1
- data/app/pb_kits/playbook/pb_button/button.html.erb +2 -3
- data/app/pb_kits/playbook/pb_button_toolbar/button_toolbar.html.erb +2 -7
- data/app/pb_kits/playbook/pb_caption/caption.html.erb +1 -6
- data/app/pb_kits/playbook/pb_card/card.html.erb +1 -7
- data/app/pb_kits/playbook/pb_card/card_body.html.erb +1 -6
- data/app/pb_kits/playbook/pb_card/card_header.html.erb +1 -6
- data/app/pb_kits/playbook/pb_checkbox/checkbox.html.erb +1 -6
- data/app/pb_kits/playbook/pb_circle_icon_button/circle_icon_button.html.erb +1 -6
- data/app/pb_kits/playbook/pb_collapsible/collapsible.html.erb +1 -6
- data/app/pb_kits/playbook/pb_collapsible/collapsible_content.html.erb +1 -6
- data/app/pb_kits/playbook/pb_collapsible/collapsible_main.html.erb +1 -7
- data/app/pb_kits/playbook/pb_contact/contact.html.erb +1 -6
- data/app/pb_kits/playbook/pb_currency/currency.html.erb +1 -6
- data/app/pb_kits/playbook/pb_currency/docs/_currency_alignment_swift.md +43 -0
- data/app/pb_kits/playbook/pb_currency/docs/_currency_props_swift.md +12 -0
- data/app/pb_kits/playbook/pb_currency/docs/_currency_size_swift.md +31 -0
- data/app/pb_kits/playbook/pb_currency/docs/example.yml +5 -0
- data/app/pb_kits/playbook/pb_dashboard_value/dashboard_value.html.erb +1 -6
- data/app/pb_kits/playbook/pb_date/date.html.erb +1 -6
- data/app/pb_kits/playbook/pb_date_picker/date_picker.html.erb +2 -6
- data/app/pb_kits/playbook/pb_date_range_inline/date_range_inline.html.erb +1 -5
- data/app/pb_kits/playbook/pb_date_range_stacked/date_range_stacked.html.erb +1 -5
- data/app/pb_kits/playbook/pb_date_range_stacked/docs/_date_range_stacked_default_swift.md +14 -0
- data/app/pb_kits/playbook/pb_date_range_stacked/docs/_date_range_stacked_props_swift.md +9 -0
- data/app/pb_kits/playbook/pb_date_range_stacked/docs/example.yml +4 -0
- data/app/pb_kits/playbook/pb_date_stacked/date_stacked.html.erb +1 -5
- data/app/pb_kits/playbook/pb_date_time/date_time.html.erb +1 -6
- data/app/pb_kits/playbook/pb_date_time_stacked/date_time_stacked.html.erb +1 -7
- data/app/pb_kits/playbook/pb_date_year_stacked/date_year_stacked.html.erb +1 -5
- data/app/pb_kits/playbook/pb_detail/detail.html.erb +1 -6
- data/app/pb_kits/playbook/pb_dialog/_dialog.scss +4 -2
- data/app/pb_kits/playbook/pb_dialog/dialog.html.erb +1 -6
- data/app/pb_kits/playbook/pb_dialog/dialog_body.html.erb +2 -7
- data/app/pb_kits/playbook/pb_dialog/dialog_footer.html.erb +1 -5
- data/app/pb_kits/playbook/pb_dialog/dialog_header.html.erb +2 -6
- data/app/pb_kits/playbook/pb_dialog/docs/example.yml +1 -1
- data/app/pb_kits/playbook/pb_dropdown/_dropdown.scss +181 -0
- data/app/pb_kits/playbook/pb_dropdown/_dropdown.tsx +266 -0
- data/app/pb_kits/playbook/pb_dropdown/context/index.tsx +5 -0
- data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_default.jsx +38 -0
- data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_default.md +1 -0
- data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_autocomplete.jsx +87 -0
- data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_autocomplete.md +1 -0
- data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_autocomplete_and_custom_display.jsx +102 -0
- data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_autocomplete_and_custom_display.md +1 -0
- data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_custom_display.jsx +104 -0
- data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_custom_display.md +5 -0
- data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_custom_options.jsx +63 -0
- data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_custom_options.md +1 -0
- data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_custom_padding.jsx +48 -0
- data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_custom_padding.md +1 -0
- data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_custom_trigger.jsx +77 -0
- data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_custom_trigger.md +1 -0
- data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_external_control.jsx +62 -0
- data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_hook.jsx +75 -0
- data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_label.jsx +39 -0
- data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_label.md +1 -0
- data/app/pb_kits/playbook/pb_dropdown/docs/example.yml +15 -0
- data/app/pb_kits/playbook/pb_dropdown/docs/index.js +10 -0
- data/app/pb_kits/playbook/pb_dropdown/dropdown.test.jsx +207 -0
- data/app/pb_kits/playbook/pb_dropdown/hooks/useDropdown.tsx +17 -0
- data/app/pb_kits/playbook/pb_dropdown/hooks/useHandleOnKeydown.tsx +61 -0
- data/app/pb_kits/playbook/pb_dropdown/subcomponents/DropdownContainer.tsx +109 -0
- data/app/pb_kits/playbook/pb_dropdown/subcomponents/DropdownOption.tsx +116 -0
- data/app/pb_kits/playbook/pb_dropdown/subcomponents/DropdownTrigger.tsx +190 -0
- data/app/pb_kits/playbook/pb_dropdown/utilities/subComponentHelper.tsx +61 -0
- data/app/pb_kits/playbook/pb_file_upload/file_upload.html.erb +1 -6
- data/app/pb_kits/playbook/pb_filter/filter.html.erb +1 -5
- data/app/pb_kits/playbook/pb_fixed_confirmation_toast/fixed_confirmation_toast.html.erb +1 -6
- data/app/pb_kits/playbook/pb_flex/flex.html.erb +1 -5
- data/app/pb_kits/playbook/pb_flex/flex_item.html.erb +2 -6
- data/app/pb_kits/playbook/pb_form_group/form_group.html.erb +1 -6
- data/app/pb_kits/playbook/pb_form_pill/form_pill.html.erb +1 -1
- data/app/pb_kits/playbook/pb_hashtag/hashtag.html.erb +1 -6
- data/app/pb_kits/playbook/pb_highlight/highlight.html.erb +1 -5
- data/app/pb_kits/playbook/pb_home_address_street/home_address_street.html.erb +1 -5
- data/app/pb_kits/playbook/pb_icon_circle/_icon_circle.scss +1 -1
- data/app/pb_kits/playbook/pb_icon_circle/_icon_circle.tsx +1 -1
- data/app/pb_kits/playbook/pb_icon_circle/icon_circle.html.erb +2 -7
- data/app/pb_kits/playbook/pb_icon_circle/icon_circle.rb +1 -1
- data/app/pb_kits/playbook/pb_icon_circle/icon_circle.test.js +3 -3
- data/app/pb_kits/playbook/pb_icon_stat_value/icon_stat_value.html.erb +1 -6
- data/app/pb_kits/playbook/pb_icon_value/icon_value.html.erb +1 -6
- data/app/pb_kits/playbook/pb_label_pill/label_pill.html.erb +1 -6
- data/app/pb_kits/playbook/pb_label_value/label_value.html.erb +1 -6
- data/app/pb_kits/playbook/pb_layout/body.html.erb +1 -5
- data/app/pb_kits/playbook/pb_layout/footer.html.erb +1 -5
- data/app/pb_kits/playbook/pb_layout/header.html.erb +1 -5
- data/app/pb_kits/playbook/pb_layout/item.html.erb +1 -5
- data/app/pb_kits/playbook/pb_layout/layout.html.erb +1 -5
- data/app/pb_kits/playbook/pb_layout/sidebar.html.erb +1 -5
- data/app/pb_kits/playbook/pb_list/_list_item.tsx +2 -2
- data/app/pb_kits/playbook/pb_list/item.html.erb +2 -8
- data/app/pb_kits/playbook/pb_list/list.html.erb +2 -8
- data/app/pb_kits/playbook/pb_loading_inline/loading_inline.html.erb +1 -6
- data/app/pb_kits/playbook/pb_message/message.html.erb +1 -6
- data/app/pb_kits/playbook/pb_message/message_mention.html.erb +1 -6
- data/app/pb_kits/playbook/pb_multi_level_select/multi_level_select.html.erb +1 -6
- data/app/pb_kits/playbook/pb_multiple_users/multiple_users.html.erb +1 -6
- data/app/pb_kits/playbook/pb_multiple_users_stacked/multiple_users_stacked.html.erb +1 -6
- data/app/pb_kits/playbook/pb_nav/item.html.erb +3 -14
- data/app/pb_kits/playbook/pb_nav/nav.html.erb +1 -6
- data/app/pb_kits/playbook/pb_online_status/online_status.html.erb +2 -6
- data/app/pb_kits/playbook/pb_pagination/pagination.html.erb +1 -6
- data/app/pb_kits/playbook/pb_passphrase/passphrase.html.erb +1 -1
- data/app/pb_kits/playbook/pb_person/person.html.erb +7 -12
- data/app/pb_kits/playbook/pb_person_contact/person_contact.html.erb +1 -6
- data/app/pb_kits/playbook/pb_pill/pill.html.erb +1 -6
- data/app/pb_kits/playbook/pb_popover/popover.html.erb +1 -6
- data/app/pb_kits/playbook/pb_progress_pills/progress_pills.html.erb +2 -6
- data/app/pb_kits/playbook/pb_progress_simple/progress_simple.html.erb +3 -6
- data/app/pb_kits/playbook/pb_progress_step/progress_step.html.erb +1 -5
- data/app/pb_kits/playbook/pb_progress_step/progress_step_item.html.erb +1 -5
- data/app/pb_kits/playbook/pb_radio/radio.html.erb +2 -8
- data/app/pb_kits/playbook/pb_section_separator/_section_separator.scss +6 -2
- data/app/pb_kits/playbook/pb_section_separator/_section_separator_mixin.scss +11 -1
- data/app/pb_kits/playbook/pb_section_separator/section_separator.html.erb +1 -6
- data/app/pb_kits/playbook/pb_select/select.html.erb +1 -5
- data/app/pb_kits/playbook/pb_selectable_card/selectable_card.html.erb +1 -5
- data/app/pb_kits/playbook/pb_selectable_card_icon/selectable_card_icon.html.erb +1 -4
- data/app/pb_kits/playbook/pb_selectable_icon/selectable_icon.html.erb +1 -5
- data/app/pb_kits/playbook/pb_selectable_list/selectable_list.html.erb +1 -6
- data/app/pb_kits/playbook/pb_selectable_list/selectable_list_item.html.erb +1 -6
- data/app/pb_kits/playbook/pb_source/source.html.erb +1 -5
- data/app/pb_kits/playbook/pb_source/source.test.js +2 -2
- data/app/pb_kits/playbook/pb_star_rating/star_rating.html.erb +1 -5
- data/app/pb_kits/playbook/pb_stat_change/stat_change.html.erb +1 -5
- data/app/pb_kits/playbook/pb_stat_value/stat_value.html.erb +1 -5
- data/app/pb_kits/playbook/pb_table/table.html.erb +2 -12
- data/app/pb_kits/playbook/pb_table/table_body.html.erb +6 -16
- data/app/pb_kits/playbook/pb_table/table_cell.html.erb +6 -16
- data/app/pb_kits/playbook/pb_table/table_head.html.erb +6 -16
- data/app/pb_kits/playbook/pb_table/table_header.html.erb +4 -13
- data/app/pb_kits/playbook/pb_table/table_row.html.erb +6 -16
- data/app/pb_kits/playbook/pb_textarea/textarea.html.erb +1 -5
- data/app/pb_kits/playbook/pb_time/time.html.erb +1 -5
- data/app/pb_kits/playbook/pb_time_range_inline/time_range_inline.html.erb +1 -5
- data/app/pb_kits/playbook/pb_time_stacked/time_stacked.html.erb +1 -5
- data/app/pb_kits/playbook/pb_timeline/item.html.erb +3 -7
- data/app/pb_kits/playbook/pb_timeline/timeline.html.erb +1 -5
- data/app/pb_kits/playbook/pb_timestamp/timestamp.html.erb +1 -6
- data/app/pb_kits/playbook/pb_title/title.html.erb +1 -6
- data/app/pb_kits/playbook/pb_title_count/title_count.html.erb +1 -6
- data/app/pb_kits/playbook/pb_title_detail/title_detail.html.erb +1 -5
- data/app/pb_kits/playbook/pb_toggle/toggle.html.erb +1 -6
- data/app/pb_kits/playbook/pb_tooltip/tooltip.html.erb +1 -5
- data/app/pb_kits/playbook/pb_typeahead/_typeahead.tsx +27 -19
- data/app/pb_kits/playbook/pb_typeahead/components/MenuList.tsx +4 -2
- data/app/pb_kits/playbook/pb_typeahead/components/index.tsx +19 -0
- data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_custom_menu_list.jsx +51 -0
- data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_with_highlight.jsx +1 -1
- data/app/pb_kits/playbook/pb_typeahead/docs/example.yml +1 -0
- data/app/pb_kits/playbook/pb_typeahead/docs/index.js +1 -0
- data/app/pb_kits/playbook/pb_user/user.html.erb +1 -6
- data/app/pb_kits/playbook/pb_user_badge/user_badge.html.erb +1 -6
- data/app/pb_kits/playbook/pb_weekday_stacked/weekday_stacked.html.erb +1 -6
- data/app/pb_kits/playbook/playbook-doc.js +2 -0
- data/app/pb_kits/playbook/tokens/_colors.scss +1 -1
- data/dist/menu.yml +5 -1
- data/dist/playbook-rails.js +6 -6
- data/lib/playbook/kit_base.rb +21 -1
- data/lib/playbook/version.rb +2 -2
- metadata +51 -6
- /data/app/pb_kits/playbook/pb_dialog/docs/{_dialog_props_table.md β _dialog_props_swift.md} +0 -0
@@ -0,0 +1,266 @@
|
|
1
|
+
import React, { useState, useRef, useEffect, ReactElement } from "react";
|
2
|
+
import classnames from "classnames";
|
3
|
+
import { buildAriaProps, buildCss, buildDataProps, buildHtmlProps } from "../utilities/props";
|
4
|
+
import { globalProps } from "../utilities/globalProps";
|
5
|
+
|
6
|
+
import Body from "../pb_body/_body";
|
7
|
+
import Caption from "../pb_caption/_caption";
|
8
|
+
|
9
|
+
import DropdownContainer from "./subcomponents/DropdownContainer";
|
10
|
+
import DropdownOption from "./subcomponents/DropdownOption";
|
11
|
+
import DropdownTrigger from "./subcomponents/DropdownTrigger";
|
12
|
+
import DropdownContext from "./context";
|
13
|
+
import useDropdown from "./hooks/useDropdown";
|
14
|
+
|
15
|
+
import {
|
16
|
+
separateChildComponents,
|
17
|
+
prepareSubcomponents,
|
18
|
+
} from "./utilities/subComponentHelper";
|
19
|
+
import { GenericObject } from "../types";
|
20
|
+
|
21
|
+
type DropdownProps = {
|
22
|
+
aria?: { [key: string]: string };
|
23
|
+
autocomplete?: boolean;
|
24
|
+
children?: React.ReactChild[] | React.ReactChild | ReactElement[];
|
25
|
+
className?: string;
|
26
|
+
dark?: boolean;
|
27
|
+
data?: { [key: string]: string };
|
28
|
+
htmlOptions?: {[key: string]: string | number | boolean | (() => void)},
|
29
|
+
id?: string;
|
30
|
+
isClosed?: boolean;
|
31
|
+
label?: string;
|
32
|
+
onSelect?: (arg: GenericObject) => null;
|
33
|
+
options: GenericObject;
|
34
|
+
triggerRef?: any;
|
35
|
+
};
|
36
|
+
|
37
|
+
const Dropdown = (props: DropdownProps) => {
|
38
|
+
const {
|
39
|
+
aria = {},
|
40
|
+
autocomplete = false,
|
41
|
+
children,
|
42
|
+
className,
|
43
|
+
dark = false,
|
44
|
+
data = {},
|
45
|
+
htmlOptions = {},
|
46
|
+
id,
|
47
|
+
isClosed = true,
|
48
|
+
label,
|
49
|
+
onSelect,
|
50
|
+
options,
|
51
|
+
triggerRef
|
52
|
+
} = props;
|
53
|
+
|
54
|
+
const ariaProps = buildAriaProps(aria);
|
55
|
+
const dataProps = buildDataProps(data);
|
56
|
+
const htmlProps = buildHtmlProps(htmlOptions);
|
57
|
+
const classes = classnames(
|
58
|
+
buildCss("pb_dropdown"),
|
59
|
+
globalProps(props),
|
60
|
+
className
|
61
|
+
);
|
62
|
+
|
63
|
+
const [isDropDownClosed, setIsDropDownClosed, toggleDropdown] = useDropdown(isClosed);
|
64
|
+
|
65
|
+
const [filterItem, setFilterItem] = useState("");
|
66
|
+
const [selected, setSelected] = useState<GenericObject>({});
|
67
|
+
const [isInputFocused, setIsInputFocused] = useState(false);
|
68
|
+
const [hasTriggerSubcomponent, setHasTriggerSubcomponent] = useState(true);
|
69
|
+
const [hasContainerSubcomponent, setHasContainerSubcomponent] =
|
70
|
+
useState(true);
|
71
|
+
//state for keyboard events
|
72
|
+
const [focusedOptionIndex, setFocusedOptionIndex] = useState(-1);
|
73
|
+
|
74
|
+
const dropdownRef = useRef(null);
|
75
|
+
const inputRef = useRef(null);
|
76
|
+
const inputWrapperRef = useRef(null);
|
77
|
+
const dropdownContainerRef = useRef(null);
|
78
|
+
|
79
|
+
const { trigger, container, otherChildren } =
|
80
|
+
separateChildComponents(children);
|
81
|
+
|
82
|
+
useEffect(() => {
|
83
|
+
// Set the parent element of the trigger to relative to allow for absolute positioning of the dropdown
|
84
|
+
//Only needed for when useDropdown hook used with external trigger
|
85
|
+
if (triggerRef?.current) {
|
86
|
+
const parentElement = triggerRef.current.parentNode;
|
87
|
+
if (parentElement) {
|
88
|
+
parentElement.style.position = 'relative';
|
89
|
+
}
|
90
|
+
}
|
91
|
+
// Handle clicks outside the dropdown
|
92
|
+
const handleClickOutside = (e: MouseEvent) => {
|
93
|
+
let targetElement = e.target as HTMLElement;
|
94
|
+
let shouldClose = true;
|
95
|
+
|
96
|
+
while (targetElement && shouldClose) {
|
97
|
+
//Only needed for when useDropdown hook used with external trigger
|
98
|
+
if (targetElement.getAttribute('data-dropdown') === 'pb-dropdown-trigger') {
|
99
|
+
shouldClose = false;
|
100
|
+
}
|
101
|
+
targetElement = targetElement.parentElement as HTMLElement;
|
102
|
+
}
|
103
|
+
if (
|
104
|
+
inputWrapperRef.current && !inputWrapperRef.current.contains(e.target) &&
|
105
|
+
(dropdownContainerRef.current && !dropdownContainerRef.current.contains(e.target)) &&
|
106
|
+
shouldClose
|
107
|
+
) {
|
108
|
+
setIsDropDownClosed(true);
|
109
|
+
setFocusedOptionIndex(-1);
|
110
|
+
setIsInputFocused(false);
|
111
|
+
}
|
112
|
+
};
|
113
|
+
|
114
|
+
window.addEventListener("click", handleClickOutside);
|
115
|
+
return () => {
|
116
|
+
window.removeEventListener("click", handleClickOutside);
|
117
|
+
};
|
118
|
+
}, []);
|
119
|
+
|
120
|
+
useEffect(() => {
|
121
|
+
setHasTriggerSubcomponent(!!trigger);
|
122
|
+
setHasContainerSubcomponent(!!container);
|
123
|
+
}, []);
|
124
|
+
|
125
|
+
// dropdown to toggle with external control
|
126
|
+
useEffect(()=> {
|
127
|
+
setIsDropDownClosed(isClosed)
|
128
|
+
},[isClosed])
|
129
|
+
|
130
|
+
const filteredOptions = options?.filter((option: GenericObject) =>
|
131
|
+
option.label.toLowerCase().includes(filterItem.toLowerCase())
|
132
|
+
);
|
133
|
+
|
134
|
+
// For keyboard accessibility: Set focus within dropdown to selected item if it exists
|
135
|
+
useEffect(() => {
|
136
|
+
if (!isDropDownClosed) {
|
137
|
+
let newIndex = 0;
|
138
|
+
if (selected && selected?.label) {
|
139
|
+
const selectedIndex = filteredOptions.findIndex((option: GenericObject) => option.label === selected.label);
|
140
|
+
if (selectedIndex >= 0) {
|
141
|
+
newIndex = selectedIndex;
|
142
|
+
}
|
143
|
+
}
|
144
|
+
setFocusedOptionIndex(newIndex);
|
145
|
+
}
|
146
|
+
}, [isDropDownClosed]);
|
147
|
+
|
148
|
+
|
149
|
+
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
150
|
+
setFilterItem(e.target.value);
|
151
|
+
setIsDropDownClosed(false);
|
152
|
+
};
|
153
|
+
|
154
|
+
const handleOptionClick = (selectedItem: GenericObject) => {
|
155
|
+
setSelected(selectedItem);
|
156
|
+
setFilterItem("");
|
157
|
+
setIsDropDownClosed(true);
|
158
|
+
onSelect && onSelect(selectedItem);
|
159
|
+
};
|
160
|
+
|
161
|
+
const handleWrapperClick = () => {
|
162
|
+
autocomplete && inputRef.current.focus();
|
163
|
+
toggleDropdown();
|
164
|
+
};
|
165
|
+
|
166
|
+
const handleBackspace = () => {
|
167
|
+
setSelected({});
|
168
|
+
onSelect && onSelect(null);
|
169
|
+
setFocusedOptionIndex(-1);
|
170
|
+
};
|
171
|
+
|
172
|
+
const componentsToRender = prepareSubcomponents({
|
173
|
+
children,
|
174
|
+
hasTriggerSubcomponent,
|
175
|
+
hasContainerSubcomponent,
|
176
|
+
trigger,
|
177
|
+
container,
|
178
|
+
otherChildren,
|
179
|
+
dark
|
180
|
+
});
|
181
|
+
|
182
|
+
|
183
|
+
return (
|
184
|
+
<div {...ariaProps}
|
185
|
+
{...dataProps}
|
186
|
+
{...htmlProps}
|
187
|
+
className={classes}
|
188
|
+
id={id}
|
189
|
+
style={triggerRef ? { position: "absolute"} : { position: "relative"}}
|
190
|
+
>
|
191
|
+
<DropdownContext.Provider
|
192
|
+
value={{
|
193
|
+
autocomplete,
|
194
|
+
dropdownContainerRef,
|
195
|
+
filteredOptions,
|
196
|
+
filterItem,
|
197
|
+
focusedOptionIndex,
|
198
|
+
handleBackspace,
|
199
|
+
handleChange,
|
200
|
+
handleOptionClick,
|
201
|
+
handleWrapperClick,
|
202
|
+
inputRef,
|
203
|
+
inputWrapperRef,
|
204
|
+
isDropDownClosed,
|
205
|
+
isInputFocused,
|
206
|
+
options,
|
207
|
+
selected,
|
208
|
+
setFocusedOptionIndex,
|
209
|
+
setIsDropDownClosed,
|
210
|
+
setIsInputFocused,
|
211
|
+
setSelected,
|
212
|
+
toggleDropdown,
|
213
|
+
triggerRef
|
214
|
+
}}
|
215
|
+
>
|
216
|
+
{label &&
|
217
|
+
<Caption
|
218
|
+
dark={dark}
|
219
|
+
marginBottom="xs"
|
220
|
+
text={label}
|
221
|
+
/>
|
222
|
+
}
|
223
|
+
<div className="dropdown_wrapper"
|
224
|
+
onBlur={() => {
|
225
|
+
// Debounce to delay the execution to prevent jumpiness in Focus state
|
226
|
+
setTimeout(() => {
|
227
|
+
if (!dropdownRef.current.contains(document.activeElement)) {
|
228
|
+
setIsInputFocused(false);
|
229
|
+
}
|
230
|
+
}, 0);
|
231
|
+
}}
|
232
|
+
onFocus={() => setIsInputFocused(true)}
|
233
|
+
ref={dropdownRef}
|
234
|
+
>
|
235
|
+
{children ? (
|
236
|
+
<>
|
237
|
+
{componentsToRender.map((component, index) => (
|
238
|
+
<React.Fragment key={index}>{component}</React.Fragment>
|
239
|
+
))}
|
240
|
+
</>
|
241
|
+
) : (
|
242
|
+
<>
|
243
|
+
<DropdownTrigger />
|
244
|
+
<DropdownContainer>
|
245
|
+
{options &&
|
246
|
+
options?.map((option: GenericObject) => (
|
247
|
+
<Dropdown.Option key={option.id}
|
248
|
+
option={option}
|
249
|
+
>
|
250
|
+
<Body text={option.label} />
|
251
|
+
</Dropdown.Option>
|
252
|
+
))}
|
253
|
+
</DropdownContainer>
|
254
|
+
</>
|
255
|
+
)}
|
256
|
+
</div>
|
257
|
+
</DropdownContext.Provider>
|
258
|
+
</div>
|
259
|
+
)
|
260
|
+
};
|
261
|
+
|
262
|
+
Dropdown.Option = DropdownOption;
|
263
|
+
Dropdown.Trigger = DropdownTrigger;
|
264
|
+
Dropdown.Container = DropdownContainer;
|
265
|
+
|
266
|
+
export default Dropdown;
|
@@ -0,0 +1,38 @@
|
|
1
|
+
import React from 'react'
|
2
|
+
import { Dropdown } from '../../'
|
3
|
+
|
4
|
+
const DropdownDefault = (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
|
+
options={options}
|
26
|
+
{...props}
|
27
|
+
>
|
28
|
+
{options.map((option) => (
|
29
|
+
<Dropdown.Option key={option.id}
|
30
|
+
option={option}
|
31
|
+
/>
|
32
|
+
))}
|
33
|
+
</Dropdown>
|
34
|
+
</div>
|
35
|
+
)
|
36
|
+
}
|
37
|
+
|
38
|
+
export default DropdownDefault
|
@@ -0,0 +1 @@
|
|
1
|
+
`options` for the Dropdown and `option` for the Dropdown.Option are the only required props for this kit. `options` must be an array of objects. Each object can contain any key/value pairs needed but 'label' and 'value' are required.
|
@@ -0,0 +1,87 @@
|
|
1
|
+
import React, { useState } from 'react'
|
2
|
+
import { Dropdown, User, Badge, FlexItem } from '../..'
|
3
|
+
|
4
|
+
const DropdownWithAutocomplete = (props) => {
|
5
|
+
// eslint-disable-next-line no-unused-vars
|
6
|
+
const [selectedOption, setSelectedOption] = useState();
|
7
|
+
|
8
|
+
const options = [
|
9
|
+
{
|
10
|
+
label: "Jasper Furniss",
|
11
|
+
value: "Jasper Furniss",
|
12
|
+
territory: "PHL",
|
13
|
+
title: "Senior UX Engineer",
|
14
|
+
id: "jasper-furniss",
|
15
|
+
status: "Offline"
|
16
|
+
},
|
17
|
+
{
|
18
|
+
label: "Ramon Ruiz",
|
19
|
+
value: "Ramon Ruiz",
|
20
|
+
territory: "PHL",
|
21
|
+
title: "Senior UX Desinger",
|
22
|
+
id: "ramon-ruiz",
|
23
|
+
status: "Away"
|
24
|
+
},
|
25
|
+
{
|
26
|
+
label: "Jason Cypret",
|
27
|
+
value: "Jason Cypret",
|
28
|
+
territory: "PHL",
|
29
|
+
title: "VP of User Experience",
|
30
|
+
id: "jason-cypret",
|
31
|
+
status: "Online"
|
32
|
+
},
|
33
|
+
{
|
34
|
+
label: "Courtney Long",
|
35
|
+
value: "Courtney Long",
|
36
|
+
territory: "PHL",
|
37
|
+
title: "UX Design Mentor",
|
38
|
+
id: "courtney-long",
|
39
|
+
status: "Online"
|
40
|
+
}
|
41
|
+
];
|
42
|
+
|
43
|
+
|
44
|
+
return (
|
45
|
+
<div>
|
46
|
+
<Dropdown autocomplete
|
47
|
+
onSelect={(selectedItem) => setSelectedOption(selectedItem)}
|
48
|
+
options={options}
|
49
|
+
{...props}
|
50
|
+
>
|
51
|
+
{options.map((option) => (
|
52
|
+
<Dropdown.Option key={option.id}
|
53
|
+
option={option}
|
54
|
+
>
|
55
|
+
<>
|
56
|
+
<FlexItem>
|
57
|
+
<User
|
58
|
+
align="left"
|
59
|
+
avatar
|
60
|
+
name={option.label}
|
61
|
+
orientation="horizontal"
|
62
|
+
territory={option.territory}
|
63
|
+
title={option.title}
|
64
|
+
/>
|
65
|
+
</FlexItem>
|
66
|
+
<FlexItem>
|
67
|
+
<Badge
|
68
|
+
rounded
|
69
|
+
text={option.status}
|
70
|
+
variant={`${
|
71
|
+
option.status === "Offline"
|
72
|
+
? "neutral"
|
73
|
+
: option.status === "Online"
|
74
|
+
? "success"
|
75
|
+
: "warning"
|
76
|
+
}`}
|
77
|
+
/>
|
78
|
+
</FlexItem>
|
79
|
+
</>
|
80
|
+
</Dropdown.Option>
|
81
|
+
))}
|
82
|
+
</Dropdown>
|
83
|
+
</div>
|
84
|
+
)
|
85
|
+
}
|
86
|
+
|
87
|
+
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.
|
@@ -0,0 +1,102 @@
|
|
1
|
+
import React, { useState } from 'react'
|
2
|
+
import { Dropdown, User, Badge, FlexItem, Avatar } from '../..'
|
3
|
+
|
4
|
+
const DropdownWithAutocompleteAndCustomDisplay = (props) => {
|
5
|
+
// eslint-disable-next-line no-unused-vars
|
6
|
+
const [selectedOption, setSelectedOption] = useState();
|
7
|
+
|
8
|
+
const options = [
|
9
|
+
{
|
10
|
+
label: "Jasper Furniss",
|
11
|
+
value: "Jasper Furniss",
|
12
|
+
territory: "PHL",
|
13
|
+
title: "Senior UX Engineer",
|
14
|
+
id: "jasper-furniss",
|
15
|
+
status: "Offline"
|
16
|
+
},
|
17
|
+
{
|
18
|
+
label: "Ramon Ruiz",
|
19
|
+
value: "Ramon Ruiz",
|
20
|
+
territory: "PHL",
|
21
|
+
title: "Senior UX Desinger",
|
22
|
+
id: "ramon-ruiz",
|
23
|
+
status: "Away"
|
24
|
+
},
|
25
|
+
{
|
26
|
+
label: "Jason Cypret",
|
27
|
+
value: "Jason Cypret",
|
28
|
+
territory: "PHL",
|
29
|
+
title: "VP of User Experience",
|
30
|
+
id: "jason-cypret",
|
31
|
+
status: "Online"
|
32
|
+
},
|
33
|
+
{
|
34
|
+
label: "Courtney Long",
|
35
|
+
value: "Courtney Long",
|
36
|
+
territory: "PHL",
|
37
|
+
title: "UX Design Mentor",
|
38
|
+
id: "courtney-long",
|
39
|
+
status: "Online"
|
40
|
+
}
|
41
|
+
];
|
42
|
+
|
43
|
+
const CustomDisplay = () => {
|
44
|
+
return (
|
45
|
+
<>
|
46
|
+
{
|
47
|
+
selectedOption && (
|
48
|
+
<Avatar
|
49
|
+
name={selectedOption.label}
|
50
|
+
size="xs"
|
51
|
+
/>
|
52
|
+
)
|
53
|
+
}
|
54
|
+
</>
|
55
|
+
)
|
56
|
+
};
|
57
|
+
|
58
|
+
return (
|
59
|
+
<div>
|
60
|
+
<Dropdown autocomplete
|
61
|
+
onSelect={(selectedItem) => setSelectedOption(selectedItem)}
|
62
|
+
options={options}
|
63
|
+
{...props}
|
64
|
+
>
|
65
|
+
<Dropdown.Trigger customDisplay={<CustomDisplay/>} />
|
66
|
+
{options.map((option) => (
|
67
|
+
<Dropdown.Option key={option.id}
|
68
|
+
option={option}
|
69
|
+
>
|
70
|
+
<>
|
71
|
+
<FlexItem>
|
72
|
+
<User
|
73
|
+
align="left"
|
74
|
+
avatar
|
75
|
+
name={option.label}
|
76
|
+
orientation="horizontal"
|
77
|
+
territory={option.territory}
|
78
|
+
title={option.title}
|
79
|
+
/>
|
80
|
+
</FlexItem>
|
81
|
+
<FlexItem>
|
82
|
+
<Badge
|
83
|
+
rounded
|
84
|
+
text={option.status}
|
85
|
+
variant={`${
|
86
|
+
option.status === "Offline"
|
87
|
+
? "neutral"
|
88
|
+
: option.status === "Online"
|
89
|
+
? "success"
|
90
|
+
: "warning"
|
91
|
+
}`}
|
92
|
+
/>
|
93
|
+
</FlexItem>
|
94
|
+
</>
|
95
|
+
</Dropdown.Option>
|
96
|
+
))}
|
97
|
+
</Dropdown>
|
98
|
+
</div>
|
99
|
+
)
|
100
|
+
}
|
101
|
+
|
102
|
+
export default DropdownWithAutocompleteAndCustomDisplay
|
@@ -0,0 +1 @@
|
|
1
|
+
`autocomplete` prop can also be used in conjunction with the `customDisplay` prop.
|
@@ -0,0 +1,104 @@
|
|
1
|
+
import React, { useState } from 'react'
|
2
|
+
import { Dropdown, User, FlexItem, Badge, Avatar } from '../../'
|
3
|
+
|
4
|
+
const DropdownWithCustomDisplay = (props) => {
|
5
|
+
const [selectedOption, setSelectedOption] = useState();
|
6
|
+
|
7
|
+
const options = [
|
8
|
+
{
|
9
|
+
label: "Jasper Furniss",
|
10
|
+
value: "Jasper Furniss",
|
11
|
+
territory: "PHL",
|
12
|
+
title: "Senior UX Engineer",
|
13
|
+
id: "jasper-furniss",
|
14
|
+
status: "Offline"
|
15
|
+
},
|
16
|
+
{
|
17
|
+
label: "Ramon Ruiz",
|
18
|
+
value: "Ramon Ruiz",
|
19
|
+
territory: "PHL",
|
20
|
+
title: "Senior UX Desinger",
|
21
|
+
id: "ramon-ruiz",
|
22
|
+
status: "Away"
|
23
|
+
},
|
24
|
+
{
|
25
|
+
label: "Jason Cypret",
|
26
|
+
value: "Jason Cypret",
|
27
|
+
territory: "PHL",
|
28
|
+
title: "VP of User Experience",
|
29
|
+
id: "jason-cypret",
|
30
|
+
status: "Online"
|
31
|
+
},
|
32
|
+
{
|
33
|
+
label: "Courtney Long",
|
34
|
+
value: "Courtney Long",
|
35
|
+
territory: "PHL",
|
36
|
+
title: "UX Design Mentor",
|
37
|
+
id: "courtney-long",
|
38
|
+
status: "Online"
|
39
|
+
}
|
40
|
+
];
|
41
|
+
|
42
|
+
const CustomDisplay = () => {
|
43
|
+
return (
|
44
|
+
<>
|
45
|
+
{
|
46
|
+
selectedOption && (
|
47
|
+
<Avatar
|
48
|
+
name={selectedOption.label}
|
49
|
+
size="xs"
|
50
|
+
/>
|
51
|
+
)
|
52
|
+
}
|
53
|
+
</>
|
54
|
+
)
|
55
|
+
};
|
56
|
+
|
57
|
+
|
58
|
+
return (
|
59
|
+
<div>
|
60
|
+
<Dropdown
|
61
|
+
onSelect={(selectedItem) => setSelectedOption(selectedItem)}
|
62
|
+
options={options}
|
63
|
+
{...props}
|
64
|
+
>
|
65
|
+
<Dropdown.Trigger customDisplay={<CustomDisplay/>}
|
66
|
+
placeholder="Select a User"
|
67
|
+
/>
|
68
|
+
{options.map((option) => (
|
69
|
+
<Dropdown.Option key={option.id}
|
70
|
+
option={option}
|
71
|
+
>
|
72
|
+
<>
|
73
|
+
<FlexItem>
|
74
|
+
<User
|
75
|
+
align="left"
|
76
|
+
avatar
|
77
|
+
name={option.label}
|
78
|
+
orientation="horizontal"
|
79
|
+
territory={option.territory}
|
80
|
+
title={option.title}
|
81
|
+
/>
|
82
|
+
</FlexItem>
|
83
|
+
<FlexItem>
|
84
|
+
<Badge dark
|
85
|
+
rounded
|
86
|
+
text={option.status}
|
87
|
+
variant={`${
|
88
|
+
option.status === "Offline"
|
89
|
+
? "neutral"
|
90
|
+
: option.status === "Online"
|
91
|
+
? "success"
|
92
|
+
: "warning"
|
93
|
+
}`}
|
94
|
+
/>
|
95
|
+
</FlexItem>
|
96
|
+
</>
|
97
|
+
</Dropdown.Option>
|
98
|
+
))}
|
99
|
+
</Dropdown>
|
100
|
+
</div>
|
101
|
+
)
|
102
|
+
}
|
103
|
+
|
104
|
+
export default DropdownWithCustomDisplay
|
@@ -0,0 +1,5 @@
|
|
1
|
+
The `customDisplay` prop can be used to customize the display of the selected item by allowing devs to pass in a component that will be rendered to the left of the default text-based display. In this example the Avatar kit is being used.
|
2
|
+
|
3
|
+
The `placeholder` prop can also be used to customize the placeholder text for the default Trigger.
|
4
|
+
|
5
|
+
The `onSelect` prop is a function that gives the dev one argument: the selected option. In this example we are using the `onSelect` to set a state with the selected option and using it to customize the `customDisplay`.
|
@@ -0,0 +1,63 @@
|
|
1
|
+
import React from 'react'
|
2
|
+
import { Dropdown, Icon, Body, FlexItem, Flex } from '../..'
|
3
|
+
|
4
|
+
const DropdownWithCustomOptions = (props) => {
|
5
|
+
|
6
|
+
const options = [
|
7
|
+
{
|
8
|
+
label: "United States",
|
9
|
+
value: "United States",
|
10
|
+
areaCode: "+1",
|
11
|
+
icon: "πΊπΈ",
|
12
|
+
id: "United-states"
|
13
|
+
},
|
14
|
+
{
|
15
|
+
label: "Canada",
|
16
|
+
value: "Canada",
|
17
|
+
areaCode: "+1",
|
18
|
+
icon: "π¨π¦",
|
19
|
+
id: "canada"
|
20
|
+
},
|
21
|
+
{
|
22
|
+
label: "Pakistan",
|
23
|
+
value: "Pakistan",
|
24
|
+
areaCode: "+92",
|
25
|
+
icon: "π΅π°",
|
26
|
+
id: "pakistan"
|
27
|
+
}
|
28
|
+
];
|
29
|
+
|
30
|
+
|
31
|
+
return (
|
32
|
+
<div>
|
33
|
+
<Dropdown
|
34
|
+
options={options}
|
35
|
+
{...props}
|
36
|
+
>
|
37
|
+
{options.map((option) => (
|
38
|
+
<Dropdown.Option key={option.id}
|
39
|
+
option={option}
|
40
|
+
>
|
41
|
+
<>
|
42
|
+
<FlexItem>
|
43
|
+
<Flex>
|
44
|
+
<Icon icon={option.icon}
|
45
|
+
paddingRight="xs"
|
46
|
+
/>
|
47
|
+
<Body text={option.label} />
|
48
|
+
</Flex>
|
49
|
+
</FlexItem>
|
50
|
+
<FlexItem>
|
51
|
+
<Body color="light"
|
52
|
+
text={option.areaCode}
|
53
|
+
/>
|
54
|
+
</FlexItem>
|
55
|
+
</>
|
56
|
+
</Dropdown.Option>
|
57
|
+
))}
|
58
|
+
</Dropdown>
|
59
|
+
</div>
|
60
|
+
)
|
61
|
+
}
|
62
|
+
|
63
|
+
export default DropdownWithCustomOptions
|
@@ -0,0 +1 @@
|
|
1
|
+
The Dropdown also allows for custom options that can be passed in as children to the `Dropdown.Option` subcomponent. If no children are passed in the `Dropdown.Option`, the kit will render each option as text by default.
|