playbook_ui 18.2.0 → 18.3.0.pre.alpha.forwardrefwarnings20879
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/pb_advanced_table/table_header.rb +1 -1
- data/app/pb_kits/playbook/pb_avatar/_avatar.tsx +3 -1
- data/app/pb_kits/playbook/pb_avatar/kit.schema.json +1 -0
- data/app/pb_kits/playbook/pb_collapsible/collapsible.test.js +1 -2
- data/app/pb_kits/playbook/pb_contact/_contact.tsx +4 -1
- data/app/pb_kits/playbook/pb_contact/kit.schema.json +1 -0
- data/app/pb_kits/playbook/pb_date/_date.tsx +3 -1
- data/app/pb_kits/playbook/pb_date/date.test.js +1 -1
- data/app/pb_kits/playbook/pb_date/kit.schema.json +1 -0
- data/app/pb_kits/playbook/pb_date_picker/date_picker.test.js +4 -0
- data/app/pb_kits/playbook/pb_date_range_inline/_date_range_inline.tsx +3 -1
- data/app/pb_kits/playbook/pb_date_range_inline/kit.schema.json +1 -0
- data/app/pb_kits/playbook/pb_date_range_stacked/_date_range_stacked.tsx +3 -1
- data/app/pb_kits/playbook/pb_date_range_stacked/kit.schema.json +1 -0
- data/app/pb_kits/playbook/pb_date_stacked/_date_stacked.tsx +4 -1
- data/app/pb_kits/playbook/pb_date_stacked/date_stacked.test.js +1 -0
- data/app/pb_kits/playbook/pb_date_stacked/kit.schema.json +1 -0
- data/app/pb_kits/playbook/pb_date_time/_date_time.tsx +3 -1
- data/app/pb_kits/playbook/pb_date_time/dateTime.test.js +1 -0
- data/app/pb_kits/playbook/pb_date_time/kit.schema.json +1 -0
- data/app/pb_kits/playbook/pb_date_time_stacked/_date_time_stacked.tsx +2 -1
- data/app/pb_kits/playbook/pb_date_time_stacked/kit.schema.json +1 -0
- data/app/pb_kits/playbook/pb_date_year_stacked/_date_year_stacked.tsx +3 -1
- data/app/pb_kits/playbook/pb_date_year_stacked/date_year_stacked.test.js +1 -0
- data/app/pb_kits/playbook/pb_date_year_stacked/kit.schema.json +1 -0
- data/app/pb_kits/playbook/pb_draggable/utilities/touchDrag.ts +10 -0
- data/app/pb_kits/playbook/pb_dropdown/_dropdown.scss +112 -0
- data/app/pb_kits/playbook/pb_dropdown/_dropdown.tsx +121 -15
- data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_draggable_pills.html.erb +51 -0
- data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_draggable_pills.jsx +62 -0
- data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_draggable_pills_rails.md +9 -0
- data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_draggable_pills_react.md +9 -0
- data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_form.jsx +50 -0
- data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_form_react.md +9 -0
- data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_option_creation.html.erb +16 -0
- data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_option_creation.jsx +27 -0
- data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_option_creation_rails.md +9 -0
- data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_option_creation_react.md +9 -0
- data/app/pb_kits/playbook/pb_dropdown/docs/_playground.json +33 -2
- data/app/pb_kits/playbook/pb_dropdown/docs/_playground.overrides.json +13 -2
- data/app/pb_kits/playbook/pb_dropdown/docs/example.yml +5 -0
- data/app/pb_kits/playbook/pb_dropdown/docs/index.js +4 -1
- data/app/pb_kits/playbook/pb_dropdown/dropdown.html.erb +2 -2
- data/app/pb_kits/playbook/pb_dropdown/dropdown.rb +24 -1
- data/app/pb_kits/playbook/pb_dropdown/dropdown.test.jsx +235 -1
- data/app/pb_kits/playbook/pb_dropdown/dropdown_creation.test.jsx +414 -0
- data/app/pb_kits/playbook/pb_dropdown/dropdown_form.test.jsx +173 -0
- data/app/pb_kits/playbook/pb_dropdown/dropdown_index.test.js +545 -0
- data/app/pb_kits/playbook/pb_dropdown/hooks/useHandleOnKeydown.tsx +22 -5
- data/app/pb_kits/playbook/pb_dropdown/index.js +260 -37
- data/app/pb_kits/playbook/pb_dropdown/keyboard_accessibility.js +3 -1
- data/app/pb_kits/playbook/pb_dropdown/kit.schema.json +42 -2
- data/app/pb_kits/playbook/pb_dropdown/subcomponents/DropdownContainer.tsx +43 -25
- data/app/pb_kits/playbook/pb_dropdown/subcomponents/DropdownFormFields.tsx +83 -0
- data/app/pb_kits/playbook/pb_dropdown/subcomponents/DropdownOption.tsx +13 -5
- data/app/pb_kits/playbook/pb_dropdown/subcomponents/MultiSelectTriggerDisplay.tsx +336 -31
- data/app/pb_kits/playbook/pb_dropdown/utilities/optionCreation.ts +21 -0
- data/app/pb_kits/playbook/pb_dropdown/utilities/pillReorder.js +281 -0
- data/app/pb_kits/playbook/pb_dropdown/utilities/quickpickDateSync.test.ts +24 -0
- data/app/pb_kits/playbook/pb_dropdown/utilities/usePillDropIndicator.ts +144 -0
- data/app/pb_kits/playbook/pb_filter/Filter/SortMenu.tsx +8 -1
- data/app/pb_kits/playbook/pb_filter/docs/_filter_one_click.html.erb +38 -0
- data/app/pb_kits/playbook/pb_filter/docs/_filter_one_click.jsx +69 -0
- data/app/pb_kits/playbook/pb_filter/docs/_filter_one_click.md +1 -0
- data/app/pb_kits/playbook/pb_filter/docs/example.yml +2 -0
- data/app/pb_kits/playbook/pb_filter/docs/index.js +2 -1
- data/app/pb_kits/playbook/pb_filter/filter.html.erb +4 -4
- data/app/pb_kits/playbook/pb_filter/filter.rb +28 -0
- data/app/pb_kits/playbook/pb_filter/filter.test.js +41 -0
- data/app/pb_kits/playbook/pb_fixed_confirmation_toast/fixed_confirmation_toast.test.js +1 -2
- data/app/pb_kits/playbook/pb_form/docs/_description_react.md +3 -0
- data/app/pb_kits/playbook/pb_form/docs/_form_default_values.jsx +898 -0
- data/app/pb_kits/playbook/pb_form/docs/_form_default_values_react.md +3 -0
- data/app/pb_kits/playbook/pb_form/docs/_form_defaults.jsx +879 -0
- data/app/pb_kits/playbook/pb_form/docs/_form_defaults_react.md +29 -0
- data/app/pb_kits/playbook/pb_form/docs/_form_loading.jsx +75 -0
- data/app/pb_kits/playbook/pb_form/docs/{_form_loading.md → _form_loading_rails.md} +1 -0
- data/app/pb_kits/playbook/pb_form/docs/_form_loading_react.md +1 -0
- data/app/pb_kits/playbook/pb_form/docs/_form_model_values.jsx +147 -0
- data/app/pb_kits/playbook/pb_form/docs/_form_model_values_react.md +16 -0
- data/app/pb_kits/playbook/pb_form/docs/_form_multi_column.jsx +164 -0
- data/app/pb_kits/playbook/pb_form/docs/_form_multi_column_react.md +3 -0
- data/app/pb_kits/playbook/pb_form/docs/_form_validation.jsx +1057 -0
- data/app/pb_kits/playbook/pb_form/docs/_form_validation_react.md +26 -0
- data/app/pb_kits/playbook/pb_form/docs/example.yml +7 -0
- data/app/pb_kits/playbook/pb_form/pb_form_validation.js +1 -0
- data/app/pb_kits/playbook/pb_form_group/_form_group.scss +1 -4
- data/app/pb_kits/playbook/pb_full_screen/_full_screen.scss +6 -0
- data/app/pb_kits/playbook/pb_full_screen/_full_screen.tsx +11 -2
- data/app/pb_kits/playbook/pb_full_screen/docs/_full_screen_advanced_table_sticky_header.jsx +73 -0
- data/app/pb_kits/playbook/pb_full_screen/docs/_full_screen_advanced_table_sticky_header.md +1 -0
- data/app/pb_kits/playbook/pb_full_screen/docs/example.yml +1 -0
- data/app/pb_kits/playbook/pb_full_screen/docs/index.js +1 -0
- data/app/pb_kits/playbook/pb_full_screen/full_screen.test.jsx +4 -2
- data/app/pb_kits/playbook/pb_image/_image.tsx +3 -1
- data/app/pb_kits/playbook/pb_image/kit.schema.json +1 -0
- data/app/pb_kits/playbook/pb_label_pill/_label_pill.tsx +3 -1
- data/app/pb_kits/playbook/pb_label_pill/kit.schema.json +1 -0
- data/app/pb_kits/playbook/pb_legend/_legend.tsx +3 -1
- data/app/pb_kits/playbook/pb_legend/kit.schema.json +1 -0
- data/app/pb_kits/playbook/pb_list/_list_item.tsx +6 -3
- data/app/pb_kits/playbook/pb_map/docs/_map_default.md +22 -3
- data/app/pb_kits/playbook/pb_multi_level_select/_multi_level_select.tsx +4 -1
- data/app/pb_kits/playbook/pb_online_status/_online_status.tsx +3 -1
- data/app/pb_kits/playbook/pb_online_status/kit.schema.json +1 -0
- data/app/pb_kits/playbook/pb_pb_bar_graph/pbbargraph.test.jsx +1 -2
- data/app/pb_kits/playbook/pb_pb_circle_chart/pb_circle_chart.test.jsx +1 -2
- data/app/pb_kits/playbook/pb_pb_gauge_chart/pb_gauge_chart.test.jsx +1 -2
- data/app/pb_kits/playbook/pb_pb_line_graph/pb_line_graph.test.jsx +1 -2
- data/app/pb_kits/playbook/pb_person/_person.tsx +3 -1
- data/app/pb_kits/playbook/pb_person/kit.schema.json +1 -0
- data/app/pb_kits/playbook/pb_pill/_pill.tsx +3 -1
- data/app/pb_kits/playbook/pb_pill/kit.schema.json +1 -0
- data/app/pb_kits/playbook/pb_progress_step/_progress_step_item.tsx +3 -1
- data/app/pb_kits/playbook/pb_rich_text_editor/docs/_playground.json +1 -1
- data/app/pb_kits/playbook/pb_rich_text_editor/docs/_playground.overrides.json +1 -1
- data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_advanced_required_indicator.md +2 -2
- data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_rails_default.md +3 -4
- data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_rails_extensions.md +1 -1
- data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_rails_markdown_support.md +1 -1
- data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_rails_required_indicator.html.erb +9 -0
- data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_rails_required_indicator.md +3 -0
- data/app/pb_kits/playbook/pb_rich_text_editor/docs/example.yml +1 -0
- data/app/pb_kits/playbook/pb_rich_text_editor/rich_text_editor.html.erb +3 -7
- data/app/pb_kits/playbook/pb_rich_text_editor/rich_text_editor_rails.js +26 -21
- data/app/pb_kits/playbook/pb_rich_text_editor/rich_text_editor_rails.test.js +85 -1
- data/app/pb_kits/playbook/pb_selectable_card_icon/_selectable_card_icon.scss +0 -9
- data/app/pb_kits/playbook/pb_selectable_card_icon/docs/_selectable_card_icon_custom.jsx +1 -1
- data/app/pb_kits/playbook/pb_selectable_icon/_selectable_icon.scss +0 -7
- data/app/pb_kits/playbook/pb_source/_source.tsx +3 -1
- data/app/pb_kits/playbook/pb_source/kit.schema.json +1 -0
- data/app/pb_kits/playbook/pb_source/source.rb +1 -1
- data/app/pb_kits/playbook/pb_stat_change/_stat_change.tsx +3 -1
- data/app/pb_kits/playbook/pb_stat_change/kit.schema.json +1 -0
- data/app/pb_kits/playbook/pb_textarea/_textarea.tsx +4 -1
- data/app/pb_kits/playbook/pb_time_range_inline/_time_range_inline.tsx +3 -1
- data/app/pb_kits/playbook/pb_time_range_inline/kit.schema.json +1 -0
- data/app/pb_kits/playbook/pb_time_stacked/_time_stacked.tsx +3 -1
- data/app/pb_kits/playbook/pb_time_stacked/kit.schema.json +1 -0
- data/app/pb_kits/playbook/pb_timeline/_item.tsx +3 -1
- data/app/pb_kits/playbook/pb_timestamp/_timestamp.tsx +3 -1
- data/app/pb_kits/playbook/pb_timestamp/kit.schema.json +1 -0
- data/app/pb_kits/playbook/pb_timestamp/timestamp.test.js +1 -0
- data/app/pb_kits/playbook/pb_typeahead/_typeahead.scss +73 -0
- data/app/pb_kits/playbook/pb_typeahead/_typeahead.test.jsx +285 -12
- data/app/pb_kits/playbook/pb_typeahead/_typeahead.tsx +22 -14
- data/app/pb_kits/playbook/pb_typeahead/components/PillRenderer.tsx +15 -11
- data/app/pb_kits/playbook/pb_typeahead/components/ValueContainer.tsx +29 -30
- data/app/pb_kits/playbook/pb_typeahead/components/usePillDropIndicator.ts +155 -0
- data/app/pb_kits/playbook/pb_typeahead/docs/_playground.json +5 -5
- data/app/pb_kits/playbook/pb_typeahead/docs/_playground.overrides.json +2 -2
- data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_draggable_pills.html.erb +0 -1
- data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_draggable_pills.jsx +0 -1
- data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_draggable_pills_rails.md +3 -3
- data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_draggable_pills_react.md +7 -0
- data/app/pb_kits/playbook/pb_typeahead/kit.schema.json +3 -3
- data/app/pb_kits/playbook/pb_typeahead/typeahead.rb +3 -3
- data/app/pb_kits/playbook/pb_user/_user.tsx +3 -1
- data/app/pb_kits/playbook/pb_user/kit.schema.json +1 -0
- data/app/pb_kits/playbook/pb_user_badge/_user_badge.tsx +3 -1
- data/app/pb_kits/playbook/pb_user_badge/kit.schema.json +1 -0
- data/app/pb_kits/playbook/pb_weekday_stacked/_weekday_stacked.tsx +3 -1
- data/app/pb_kits/playbook/pb_weekday_stacked/kit.schema.json +1 -0
- data/app/pb_kits/playbook/pb_weekday_stacked/weekday_stacked.test.jsx +1 -0
- data/app/pb_kits/playbook/utilities/test/globalProps/onClick.test.js +302 -0
- data/app/pb_kits/playbook/utilities/test-utils.js +0 -1
- data/dist/ai/all-schemas.json +108 -15
- data/dist/ai/external-dependencies.json +1 -1
- data/dist/ai/forms.json +2 -2
- data/dist/ai/index.json +3 -3
- data/dist/ai/kits/avatar.schema.json +1 -0
- data/dist/ai/kits/contact.schema.json +1 -0
- data/dist/ai/kits/date.schema.json +1 -0
- data/dist/ai/kits/date_range_inline.schema.json +1 -0
- data/dist/ai/kits/date_range_stacked.schema.json +1 -0
- data/dist/ai/kits/date_stacked.schema.json +1 -0
- data/dist/ai/kits/date_time.schema.json +1 -0
- data/dist/ai/kits/date_time_stacked.schema.json +1 -0
- data/dist/ai/kits/date_year_stacked.schema.json +1 -0
- data/dist/ai/kits/dropdown.schema.json +81 -11
- data/dist/ai/kits/image.schema.json +1 -0
- data/dist/ai/kits/label_pill.schema.json +1 -0
- data/dist/ai/kits/legend.schema.json +1 -0
- data/dist/ai/kits/online_status.schema.json +1 -0
- data/dist/ai/kits/person.schema.json +1 -0
- data/dist/ai/kits/pill.schema.json +1 -0
- data/dist/ai/kits/rich_text_editor.schema.json +1 -1
- data/dist/ai/kits/source.schema.json +1 -0
- data/dist/ai/kits/stat_change.schema.json +1 -0
- data/dist/ai/kits/time_range_inline.schema.json +1 -0
- data/dist/ai/kits/time_stacked.schema.json +1 -0
- data/dist/ai/kits/timestamp.schema.json +1 -0
- data/dist/ai/kits/typeahead.schema.json +3 -3
- data/dist/ai/kits/user.schema.json +1 -0
- data/dist/ai/kits/user_badge.schema.json +1 -0
- data/dist/ai/kits/weekday_stacked.schema.json +1 -0
- data/dist/ai/playgrounds/dropdown.json +1 -1
- data/dist/ai/playgrounds/index.json +1 -1
- data/dist/ai/playgrounds/rich_text_editor.json +1 -1
- data/dist/ai/playgrounds/typeahead.json +1 -1
- data/dist/chunks/_pagination-CARbC_IN.js +1 -0
- data/dist/chunks/{_pb_line_graph-Du7xE9vl.js → _pb_line_graph-BKwbOeqo.js} +1 -1
- data/dist/chunks/{_detail-Bsvl694i.js → _tooltip-UkcgcThO.js} +2 -2
- data/dist/chunks/_typeahead-D_li2Zx8.js +1 -0
- data/dist/chunks/{globalProps-B120NbSJ.js → globalProps-BivjwRew.js} +1 -1
- data/dist/chunks/index-4jrZjexD.js +1 -0
- data/dist/chunks/index-BAy61y8S.js +1 -0
- data/dist/chunks/index-Cb_SDipk.js +1 -0
- data/dist/chunks/index-D3RdsDp7.js +1 -0
- data/dist/chunks/index-D8PZIesy.js +1 -0
- data/dist/chunks/index-DIoc-aua.js +1 -0
- data/dist/chunks/index-qex65UMn.js +1 -0
- data/dist/chunks/lib-C50eCpoN.js +29 -0
- data/dist/chunks/{time_picker_helper-CYGg0UQ0.js → time_picker_helper-DfKvHCJu.js} +1 -1
- data/dist/chunks/vendor.js +2 -2
- data/dist/menu.yml +1 -1
- data/dist/playbook-rails-react-bindings.js +1 -1
- data/dist/playbook-rails.js +1 -1
- data/dist/playbook.css +1 -1
- data/lib/playbook/version.rb +2 -2
- metadata +80 -21
- data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_draggable_pills.md +0 -7
- data/dist/chunks/_table-Bf6LGyL9.js +0 -1
- data/dist/chunks/_typeahead-w6S5Vx4b.js +0 -1
- data/dist/chunks/lib-Buo6zHEw.js +0 -29
- /data/app/pb_kits/playbook/pb_form/docs/{_description.md → _description_rails.md} +0 -0
- /data/app/pb_kits/playbook/pb_form/docs/{_form_default_values.md → _form_default_values_rails.md} +0 -0
- /data/app/pb_kits/playbook/pb_form/docs/{_form_defaults.md → _form_defaults_rails.md} +0 -0
- /data/app/pb_kits/playbook/pb_form/docs/{_form_model_values.md → _form_model_values_rails.md} +0 -0
- /data/app/pb_kits/playbook/pb_form/docs/{_form_multi_column.md → _form_multi_column_rails.md} +0 -0
- /data/app/pb_kits/playbook/pb_form/docs/{_form_validation.md → _form_validation_rails.md} +0 -0
|
@@ -12,8 +12,10 @@ import DropdownContainer from "./subcomponents/DropdownContainer";
|
|
|
12
12
|
import DropdownContext from "./context";
|
|
13
13
|
import DropdownOption from "./subcomponents/DropdownOption";
|
|
14
14
|
import DropdownTrigger from "./subcomponents/DropdownTrigger";
|
|
15
|
+
import DropdownFormFields from "./subcomponents/DropdownFormFields";
|
|
15
16
|
import useDropdown from "./hooks/useDropdown";
|
|
16
17
|
import useAsyncOptions, { LoadOptions } from "./hooks/useAsyncOptions";
|
|
18
|
+
import { getCreationOption, matchesOptionExactly } from "./utilities/optionCreation";
|
|
17
19
|
import getQuickPickOptions from "./quickpick";
|
|
18
20
|
|
|
19
21
|
import {
|
|
@@ -99,6 +101,7 @@ type CustomQuickPickDates = {
|
|
|
99
101
|
type InputChangeReason = "input" | "clear";
|
|
100
102
|
|
|
101
103
|
type DropdownProps = {
|
|
104
|
+
allowOptionCreation?: boolean;
|
|
102
105
|
aria?: { [key: string]: string };
|
|
103
106
|
async?: boolean;
|
|
104
107
|
loadOptions?: LoadOptions;
|
|
@@ -122,6 +125,9 @@ type DropdownProps = {
|
|
|
122
125
|
data?: { [key: string]: string };
|
|
123
126
|
defaultValue?: GenericObject;
|
|
124
127
|
error?: string;
|
|
128
|
+
enableFormSupport?: boolean;
|
|
129
|
+
enablePillReorder?: boolean;
|
|
130
|
+
externalDragHandle?: boolean;
|
|
125
131
|
htmlOptions?: { [key: string]: string | number | boolean | (() => void) },
|
|
126
132
|
id?: string;
|
|
127
133
|
isClosed?: boolean;
|
|
@@ -134,6 +140,7 @@ type DropdownProps = {
|
|
|
134
140
|
options?: GenericObject;
|
|
135
141
|
placeholder?: string;
|
|
136
142
|
separators?: boolean;
|
|
143
|
+
showPillIndex?: boolean;
|
|
137
144
|
variant?: "default" | "subtle" | "quickpick";
|
|
138
145
|
rangeEndsToday?: boolean;
|
|
139
146
|
controlsStartId?: string;
|
|
@@ -143,6 +150,8 @@ type DropdownProps = {
|
|
|
143
150
|
fontColor?: string;
|
|
144
151
|
};
|
|
145
152
|
requiredIndicator?: boolean;
|
|
153
|
+
required?: boolean;
|
|
154
|
+
validationMessage?: string;
|
|
146
155
|
};
|
|
147
156
|
|
|
148
157
|
interface DropdownComponent
|
|
@@ -154,6 +163,7 @@ interface DropdownComponent
|
|
|
154
163
|
|
|
155
164
|
let Dropdown = (props: DropdownProps, ref: any): React.ReactElement | null => {
|
|
156
165
|
const {
|
|
166
|
+
allowOptionCreation = false,
|
|
157
167
|
aria = {},
|
|
158
168
|
async = false,
|
|
159
169
|
loadOptions,
|
|
@@ -163,7 +173,7 @@ let Dropdown = (props: DropdownProps, ref: any): React.ReactElement | null => {
|
|
|
163
173
|
onInputChange,
|
|
164
174
|
renderOption,
|
|
165
175
|
value,
|
|
166
|
-
autocomplete = false,
|
|
176
|
+
autocomplete: autocompleteProp = false,
|
|
167
177
|
blankSelection = '',
|
|
168
178
|
children,
|
|
169
179
|
className,
|
|
@@ -175,7 +185,10 @@ let Dropdown = (props: DropdownProps, ref: any): React.ReactElement | null => {
|
|
|
175
185
|
data = {},
|
|
176
186
|
defaultValue = {},
|
|
177
187
|
disabled = false,
|
|
178
|
-
error,
|
|
188
|
+
error: errorProp,
|
|
189
|
+
enableFormSupport = false,
|
|
190
|
+
enablePillReorder = false,
|
|
191
|
+
externalDragHandle = false,
|
|
179
192
|
htmlOptions = {},
|
|
180
193
|
id,
|
|
181
194
|
isClosed = true,
|
|
@@ -188,15 +201,27 @@ let Dropdown = (props: DropdownProps, ref: any): React.ReactElement | null => {
|
|
|
188
201
|
onSelect,
|
|
189
202
|
options,
|
|
190
203
|
placeholder,
|
|
204
|
+
showPillIndex = false,
|
|
191
205
|
rangeEndsToday = false,
|
|
192
206
|
controlsStartId,
|
|
193
207
|
controlsEndId,
|
|
194
208
|
separators = true,
|
|
195
209
|
variant = "default",
|
|
196
210
|
activeStyle,
|
|
197
|
-
requiredIndicator = false
|
|
211
|
+
requiredIndicator = false,
|
|
212
|
+
required = false,
|
|
213
|
+
validationMessage = "Please fill out this field."
|
|
198
214
|
} = props;
|
|
199
215
|
|
|
216
|
+
const isPillReorderEnabled = Boolean(enablePillReorder && multiSelect);
|
|
217
|
+
|
|
218
|
+
const creationEnabled = allowOptionCreation && variant !== "quickpick";
|
|
219
|
+
const autocomplete = autocompleteProp || creationEnabled;
|
|
220
|
+
|
|
221
|
+
const [formInvalid, setFormInvalid] = useState(false);
|
|
222
|
+
const formError = formInvalid ? validationMessage : "";
|
|
223
|
+
const error = enableFormSupport ? errorProp || formError : errorProp;
|
|
224
|
+
|
|
200
225
|
const ariaProps = buildAriaProps(aria);
|
|
201
226
|
const dataProps = buildDataProps(data);
|
|
202
227
|
const htmlProps = buildHtmlProps(htmlOptions);
|
|
@@ -365,6 +390,11 @@ let Dropdown = (props: DropdownProps, ref: any): React.ReactElement | null => {
|
|
|
365
390
|
? [selected]
|
|
366
391
|
: [];
|
|
367
392
|
|
|
393
|
+
const hasFormSelection = enableFormSupport && selectedArray.some((option) => String(option.id ?? option.value ?? "") !== "");
|
|
394
|
+
useEffect(() => {
|
|
395
|
+
if (!enableFormSupport || hasFormSelection || !required) setFormInvalid(false);
|
|
396
|
+
}, [enableFormSupport, hasFormSelection, required]);
|
|
397
|
+
|
|
368
398
|
const selectedOptionIds = useMemo(() => (
|
|
369
399
|
multiSelect && getOptionValue
|
|
370
400
|
? new Set(selectedArray.map((option) => String(getOptionValue(option))))
|
|
@@ -415,7 +445,16 @@ let Dropdown = (props: DropdownProps, ref: any): React.ReactElement | null => {
|
|
|
415
445
|
return optionsWithBlankSelection.filter((option: GenericObject) => !selectedArray.some((sel) => isSameOption(sel, option)));
|
|
416
446
|
}, [optionsWithBlankSelection, selectedArray, multiSelect, isSameOption, selectedOptionIds, getOptionValue]);
|
|
417
447
|
|
|
418
|
-
const
|
|
448
|
+
const searchIsPending = loading || (asyncEnabled && asyncStatus === "loading");
|
|
449
|
+
const searchFailed = asyncEnabled && asyncStatus === "error";
|
|
450
|
+
const canCreateOption = creationEnabled && !disabled && !searchIsPending && !searchFailed;
|
|
451
|
+
let creationOption: GenericObject | null = null;
|
|
452
|
+
if (canCreateOption) {
|
|
453
|
+
const existingOptions = Array.isArray(dropdownOptions) ? dropdownOptions : [];
|
|
454
|
+
creationOption = getCreationOption(filterItem, [...existingOptions, ...selectedArray]);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
const matchingOptions = useMemo(() => {
|
|
419
458
|
const selectedLabel =
|
|
420
459
|
!multiSelect &&
|
|
421
460
|
!Array.isArray(selected) &&
|
|
@@ -431,9 +470,29 @@ let Dropdown = (props: DropdownProps, ref: any): React.ReactElement | null => {
|
|
|
431
470
|
const filterText =
|
|
432
471
|
selectedLabel && filterItem === selectedLabel ? "" : filterItem;
|
|
433
472
|
return availableOptions.filter((opt: GenericObject) =>
|
|
434
|
-
String(opt.label).toLowerCase().includes(filterText.toLowerCase())
|
|
473
|
+
String(opt.label).toLowerCase().includes(filterText.toLowerCase()) ||
|
|
474
|
+
(creationEnabled && matchesOptionExactly(opt, filterText))
|
|
435
475
|
);
|
|
436
|
-
}, [availableOptions, filterItem, multiSelect, selected, asyncEnabled, autocomplete]);
|
|
476
|
+
}, [availableOptions, filterItem, multiSelect, selected, asyncEnabled, autocomplete, creationEnabled]);
|
|
477
|
+
|
|
478
|
+
const filteredOptions = creationOption ? [...matchingOptions, creationOption] : matchingOptions;
|
|
479
|
+
const inputShowsSelection = !multiSelect && !Array.isArray(selected) && filterItem === selected?.label;
|
|
480
|
+
let creationFocusIndex = -1;
|
|
481
|
+
if (creationEnabled && creationOption) {
|
|
482
|
+
creationFocusIndex = matchingOptions.length;
|
|
483
|
+
} else if (creationEnabled) {
|
|
484
|
+
creationFocusIndex = matchingOptions.findIndex((option: GenericObject) => !option.disabled);
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
useEffect(() => {
|
|
488
|
+
if (!creationEnabled) return;
|
|
489
|
+
if (isDropDownClosed || !filterItem.trim() || inputShowsSelection) {
|
|
490
|
+
setFocusedOptionIndex(-1);
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
setFocusedOptionIndex(creationFocusIndex);
|
|
495
|
+
}, [creationEnabled, isDropDownClosed, filterItem, inputShowsSelection, creationFocusIndex]);
|
|
437
496
|
|
|
438
497
|
// Empty copy follows the settled search. Hiding already-selected rows must not turn a
|
|
439
498
|
// successful result list into "No results found", including the last pick while the menu stays open.
|
|
@@ -441,18 +500,25 @@ let Dropdown = (props: DropdownProps, ref: any): React.ReactElement | null => {
|
|
|
441
500
|
? noOptionsText || "No results found"
|
|
442
501
|
: "";
|
|
443
502
|
|
|
503
|
+
let asyncStatusText = "";
|
|
504
|
+
if (asyncEnabled) {
|
|
505
|
+
if (searchIsPending) asyncStatusText = "Loading…";
|
|
506
|
+
else if (searchFailed) asyncStatusText = "Unable to load options";
|
|
507
|
+
else if (!creationOption) asyncStatusText = asyncEmptyCopy;
|
|
508
|
+
}
|
|
509
|
+
|
|
444
510
|
// A keystroke clears the highlight. Focus the first result once the next list arrives,
|
|
445
511
|
// including options the application loads itself.
|
|
446
512
|
const asyncResultOptions = loadOptions ? loadedOptions : options;
|
|
447
513
|
useEffect(() => {
|
|
448
|
-
if (asyncEnabled && !isDropDownClosed) {
|
|
514
|
+
if (!creationEnabled && asyncEnabled && !isDropDownClosed) {
|
|
449
515
|
setFocusedOptionIndex(filteredOptions.length ? 0 : -1);
|
|
450
516
|
}
|
|
451
|
-
}, [asyncResultOptions]);
|
|
517
|
+
}, [asyncResultOptions, creationEnabled]);
|
|
452
518
|
|
|
453
519
|
// For keyboard accessibility: Set focus within dropdown to selected item if it exists
|
|
454
520
|
useEffect(() => {
|
|
455
|
-
if (!isDropDownClosed) {
|
|
521
|
+
if (!creationEnabled && !isDropDownClosed) {
|
|
456
522
|
let newIndex = 0;
|
|
457
523
|
if (selected && !Array.isArray(selected) && selected.label) {
|
|
458
524
|
const selectedIndex = filteredOptions.findIndex((option: GenericObject) => isSameOption(option, selected));
|
|
@@ -462,7 +528,7 @@ let Dropdown = (props: DropdownProps, ref: any): React.ReactElement | null => {
|
|
|
462
528
|
}
|
|
463
529
|
setFocusedOptionIndex(newIndex);
|
|
464
530
|
}
|
|
465
|
-
}, [isDropDownClosed]);
|
|
531
|
+
}, [isDropDownClosed, creationEnabled]);
|
|
466
532
|
|
|
467
533
|
// Auto-position dropdown above/below based on available space or fixed/absolute when portaled to body / floating root
|
|
468
534
|
useLayoutEffect(() => {
|
|
@@ -580,10 +646,12 @@ let Dropdown = (props: DropdownProps, ref: any): React.ReactElement | null => {
|
|
|
580
646
|
}
|
|
581
647
|
} else {
|
|
582
648
|
setSelected(clickedItem);
|
|
583
|
-
//
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
649
|
+
// Plain uncontrolled autocomplete clears its query after selection.
|
|
650
|
+
const keepSelectedLabel = autocomplete && (creationEnabled || asyncEnabled || isControlled);
|
|
651
|
+
const nextQuery = keepSelectedLabel
|
|
652
|
+
? (isControlled ? (selected as GenericObject)?.label : clickedItem.label) || ""
|
|
653
|
+
: "";
|
|
654
|
+
setFilterItem(nextQuery);
|
|
587
655
|
if (shouldCloseOnClick) {
|
|
588
656
|
setIsDropDownClosed(true);
|
|
589
657
|
}
|
|
@@ -724,6 +792,27 @@ let Dropdown = (props: DropdownProps, ref: any): React.ReactElement | null => {
|
|
|
724
792
|
}
|
|
725
793
|
}, [variant, initialSelected, controlsStartId, controlsEndId]);
|
|
726
794
|
|
|
795
|
+
const handleFormInvalid = () => {
|
|
796
|
+
setFormInvalid(true);
|
|
797
|
+
const focusTarget = inputRef.current || inputWrapperRef.current;
|
|
798
|
+
focusTarget?.focus();
|
|
799
|
+
};
|
|
800
|
+
|
|
801
|
+
const handleFormReset = () => {
|
|
802
|
+
const hasDefaultSelection = Object.keys(initialSelected).length > 0;
|
|
803
|
+
const resetValue = multiSelect || hasDefaultSelection ? initialSelected : null;
|
|
804
|
+
const resetQuery = autocomplete && !multiSelect
|
|
805
|
+
? (initialSelected as GenericObject).label || ""
|
|
806
|
+
: "";
|
|
807
|
+
|
|
808
|
+
setSelected(initialSelected);
|
|
809
|
+
handleSelectionChange(resetValue);
|
|
810
|
+
setFilterItem(resetQuery);
|
|
811
|
+
setFormInvalid(false);
|
|
812
|
+
setIsDropDownClosed(true);
|
|
813
|
+
if (asyncEnabled) clearAsync();
|
|
814
|
+
};
|
|
815
|
+
|
|
727
816
|
return (
|
|
728
817
|
<div {...ariaProps}
|
|
729
818
|
{...dataProps}
|
|
@@ -738,7 +827,9 @@ let Dropdown = (props: DropdownProps, ref: any): React.ReactElement | null => {
|
|
|
738
827
|
<DropdownContext.Provider
|
|
739
828
|
value={{
|
|
740
829
|
asyncEnabled,
|
|
741
|
-
asyncStatus:
|
|
830
|
+
asyncStatus: asyncStatusText,
|
|
831
|
+
creationEnabled,
|
|
832
|
+
creationOption,
|
|
742
833
|
renderOption,
|
|
743
834
|
optionKey,
|
|
744
835
|
isControlled,
|
|
@@ -754,6 +845,8 @@ let Dropdown = (props: DropdownProps, ref: any): React.ReactElement | null => {
|
|
|
754
845
|
disabled,
|
|
755
846
|
error,
|
|
756
847
|
errorId,
|
|
848
|
+
enablePillReorder: isPillReorderEnabled,
|
|
849
|
+
externalDragHandle,
|
|
757
850
|
filterItem,
|
|
758
851
|
filteredOptions,
|
|
759
852
|
focusedOptionIndex,
|
|
@@ -782,9 +875,22 @@ let Dropdown = (props: DropdownProps, ref: any): React.ReactElement | null => {
|
|
|
782
875
|
setIsDropDownClosed,
|
|
783
876
|
setIsInputFocused,
|
|
784
877
|
setSelected,
|
|
878
|
+
showPillIndex,
|
|
785
879
|
toggleDropdown
|
|
786
880
|
}}
|
|
787
881
|
>
|
|
882
|
+
{enableFormSupport && (
|
|
883
|
+
<DropdownFormFields
|
|
884
|
+
disabled={disabled}
|
|
885
|
+
multiSelect={multiSelect}
|
|
886
|
+
name={name}
|
|
887
|
+
onInvalid={handleFormInvalid}
|
|
888
|
+
onReset={handleFormReset}
|
|
889
|
+
required={required}
|
|
890
|
+
selected={selectedArray}
|
|
891
|
+
validationMessage={validationMessage}
|
|
892
|
+
/>
|
|
893
|
+
)}
|
|
788
894
|
{label && (
|
|
789
895
|
<label
|
|
790
896
|
data-dropdown="pb-dropdown-label"
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
<%
|
|
2
|
+
options = [
|
|
3
|
+
{ label: 'Year', value: 'year', id: 'year' },
|
|
4
|
+
{ label: 'Territory', value: 'territory', id: 'territory' },
|
|
5
|
+
{ label: 'Branch', value: 'branch', id: 'branch' },
|
|
6
|
+
{ label: 'Product', value: 'product', id: 'product' },
|
|
7
|
+
]
|
|
8
|
+
%>
|
|
9
|
+
|
|
10
|
+
<%= pb_rails("body", props: { text: "Select services, then drag the pills to reorder them.", margin_bottom: "xs" }) %>
|
|
11
|
+
|
|
12
|
+
<%= pb_rails("dropdown", props: {
|
|
13
|
+
id: "dropdown-draggable-pills-example",
|
|
14
|
+
default_value: [options[0], options[1], options[2]],
|
|
15
|
+
enable_pill_reorder: true,
|
|
16
|
+
show_pill_index: true,
|
|
17
|
+
multi_select: true,
|
|
18
|
+
label: "Services",
|
|
19
|
+
name: :services,
|
|
20
|
+
options: options,
|
|
21
|
+
placeholder: "Add services...",
|
|
22
|
+
}) %>
|
|
23
|
+
|
|
24
|
+
<%= pb_rails("body", props: {
|
|
25
|
+
id: "dropdown-draggable-order",
|
|
26
|
+
color: "lighter",
|
|
27
|
+
margin_top: "xs",
|
|
28
|
+
text: "Current order: Year -> Territory -> Branch",
|
|
29
|
+
}) %>
|
|
30
|
+
|
|
31
|
+
<script>
|
|
32
|
+
(() => {
|
|
33
|
+
const dropdownId = "dropdown-draggable-pills-example";
|
|
34
|
+
const orderElement = document.getElementById("dropdown-draggable-order");
|
|
35
|
+
|
|
36
|
+
const updateOrderText = (detail) => {
|
|
37
|
+
if (!orderElement || !Array.isArray(detail)) return;
|
|
38
|
+
orderElement.textContent = `Current order: ${detail.map((item) => item.label).join(" -> ")}`;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
document.addEventListener("pb:dropdown:reordered", (event) => {
|
|
42
|
+
if (event.target.id !== dropdownId) return;
|
|
43
|
+
updateOrderText(event.detail);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
document.addEventListener("pb:dropdown:selected", (event) => {
|
|
47
|
+
if (event.target.id !== dropdownId) return;
|
|
48
|
+
updateOrderText(event.detail);
|
|
49
|
+
});
|
|
50
|
+
})();
|
|
51
|
+
</script>
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import React, { useState } from 'react'
|
|
2
|
+
import { Body, Button, Dropdown, Flex } from 'playbook-ui'
|
|
3
|
+
|
|
4
|
+
const options = [
|
|
5
|
+
{ label: 'Year', value: 'year', id: 'year' },
|
|
6
|
+
{ label: 'Territory', value: 'territory', id: 'territory' },
|
|
7
|
+
{ label: 'Branch', value: 'branch', id: 'branch' },
|
|
8
|
+
{ label: 'Product', value: 'product', id: 'product' },
|
|
9
|
+
]
|
|
10
|
+
|
|
11
|
+
const DropdownDraggablePills = (props) => {
|
|
12
|
+
const [value, setValue] = useState([
|
|
13
|
+
options[0],
|
|
14
|
+
options[1],
|
|
15
|
+
options[2],
|
|
16
|
+
])
|
|
17
|
+
|
|
18
|
+
const currentOrder = value.map((item) => item.label).join(' -> ')
|
|
19
|
+
|
|
20
|
+
const handleReset = () => {
|
|
21
|
+
setValue([options[0], options[1], options[2]])
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<Flex
|
|
26
|
+
align="stretch"
|
|
27
|
+
gap="sm"
|
|
28
|
+
orientation="column"
|
|
29
|
+
>
|
|
30
|
+
<Flex
|
|
31
|
+
align="center"
|
|
32
|
+
justify="between"
|
|
33
|
+
>
|
|
34
|
+
<Body text="Select services, then drag the pills to reorder them." />
|
|
35
|
+
<Button
|
|
36
|
+
onClick={handleReset}
|
|
37
|
+
text="Reset"
|
|
38
|
+
variant="secondary"
|
|
39
|
+
/>
|
|
40
|
+
</Flex>
|
|
41
|
+
|
|
42
|
+
<Dropdown
|
|
43
|
+
enablePillReorder
|
|
44
|
+
label="Services"
|
|
45
|
+
multiSelect
|
|
46
|
+
onChange={({ target }) => setValue(target.value || [])}
|
|
47
|
+
options={options}
|
|
48
|
+
placeholder="Add services..."
|
|
49
|
+
showPillIndex
|
|
50
|
+
value={value}
|
|
51
|
+
{...props}
|
|
52
|
+
/>
|
|
53
|
+
|
|
54
|
+
<Body
|
|
55
|
+
color="lighter"
|
|
56
|
+
text={`Current order: ${currentOrder}`}
|
|
57
|
+
/>
|
|
58
|
+
</Flex>
|
|
59
|
+
)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export default DropdownDraggablePills
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
Use `enable_pill_reorder: true` with `show_pill_index: true` to let users drag selected multi-select pills into a new order. By default, each pill shows a grip icon and can be dragged from anywhere on the pill. Set `external_drag_handle: true` to use an external grip handle instead (drag only from the handle).
|
|
2
|
+
|
|
3
|
+
While dragging, pills stay in place, a ghost follows the pointer, and a blue line marks where the pill will land. Order is committed on drop.
|
|
4
|
+
|
|
5
|
+
The `pb:dropdown:reordered` custom event receives the reordered value array. Submitted form values (hidden inputs) preserve the new order. `pb:dropdown:selected` also fires with the updated order.
|
|
6
|
+
|
|
7
|
+
Keyboard reorder: focus a pill, then press ArrowLeft or ArrowRight to move it one position.
|
|
8
|
+
|
|
9
|
+
Works with Multi-Select and Multi-Select + Autocomplete Dropdown variants only, and is disabled by default.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
Use `enablePillReorder` to let users drag selected multi-select pills into a new order. Pair with `showPillIndex` to display numbered prefixes that update automatically after reorder or removal.
|
|
2
|
+
|
|
3
|
+
By default, each pill shows a grip icon and can be dragged from anywhere on the pill. Set `externalDragHandle` to `true` to use an external grip handle instead (drag only from the handle).
|
|
4
|
+
|
|
5
|
+
While dragging, pills stay in place, a ghost follows the pointer, and a blue line marks where the pill will land. Order is committed on drop. The `onChange` callback receives the reordered value array.
|
|
6
|
+
|
|
7
|
+
Keyboard reorder: focus a pill, then press ArrowLeft or ArrowRight to move it one position.
|
|
8
|
+
|
|
9
|
+
Works with Multi-Select and Multi-Select + Autocomplete. Dropdown variants only, and is disabled by default.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import React, { useState } from 'react'
|
|
2
|
+
import Dropdown from '../_dropdown'
|
|
3
|
+
import Button from '../../pb_button/_button'
|
|
4
|
+
import Body from '../../pb_body/_body'
|
|
5
|
+
|
|
6
|
+
const DropdownForm = () => {
|
|
7
|
+
const [submittedTags, setSubmittedTags] = useState(null)
|
|
8
|
+
const options = [
|
|
9
|
+
{ id: 'product', label: 'Product', value: 'product' },
|
|
10
|
+
{ id: 'support', label: 'Support', value: 'support' },
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
const handleSubmit = (event) => {
|
|
14
|
+
event.preventDefault()
|
|
15
|
+
setSubmittedTags(new FormData(event.currentTarget).getAll('tags[]'))
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<form onReset={() => setSubmittedTags(null)}
|
|
20
|
+
onSubmit={handleSubmit}
|
|
21
|
+
>
|
|
22
|
+
<Dropdown
|
|
23
|
+
enableFormSupport
|
|
24
|
+
label="Tags"
|
|
25
|
+
marginBottom="md"
|
|
26
|
+
multiSelect
|
|
27
|
+
name="tags[]"
|
|
28
|
+
options={options}
|
|
29
|
+
required
|
|
30
|
+
requiredIndicator
|
|
31
|
+
validationMessage="Select at least one tag."
|
|
32
|
+
/>
|
|
33
|
+
<Button htmlType="submit"
|
|
34
|
+
text="Submit"
|
|
35
|
+
/>
|
|
36
|
+
<Button htmlType="reset"
|
|
37
|
+
marginLeft="xs"
|
|
38
|
+
text="Reset"
|
|
39
|
+
variant="secondary"
|
|
40
|
+
/>
|
|
41
|
+
{submittedTags && (
|
|
42
|
+
<Body marginTop="sm"
|
|
43
|
+
text={`Submitted tags: ${submittedTags.join(', ')}`}
|
|
44
|
+
/>
|
|
45
|
+
)}
|
|
46
|
+
</form>
|
|
47
|
+
)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export default DropdownForm
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
Set `enableFormSupport` to opt into React native form submission, required validation, and reset handling. The prop defaults to `false`. Passing `name` or `required` alone does not enable native form behavior. Option creation is a separate feature controlled by `allowOptionCreation`.
|
|
2
|
+
|
|
3
|
+
With `enableFormSupport` enabled and `name` provided, Dropdown renders named hidden inputs. Each selected option submits its `id`, falling back to `value`. For `multiSelect`, each selection gets a separate input and the name ends in `[]`; an existing `[]` suffix is preserved. Disabled Dropdowns do not submit values. An empty selection submits an empty string.
|
|
4
|
+
|
|
5
|
+
The example below requires at least one tag. Submit an empty selection to see validation, select tags and submit to see the values from `FormData`, or use Reset to restore the default selection.
|
|
6
|
+
|
|
7
|
+
Use `required` for native form constraint validation, `validationMessage` for the inline required message, and `requiredIndicator` for the visual asterisk. Invalid submissions focus the trigger or autocomplete input. Typing a query does not satisfy required validation; select an option first. The unnamed validation input never submits query text.
|
|
8
|
+
|
|
9
|
+
Native form reset restores `defaultValue`, clears the query and validation message, and closes the menu. A controlled Dropdown reports that reset through its normal selection callbacks; the parent must update `value`. Canceling the reset event preserves the selection. In a controlled form library, pass `value`, `name`, and `onChange`; Dropdown sends `{ target: { name, value } }`, with an option object or array as `value`. The forwarded ref exposes `clearSelected`, rather than a native input ref.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<%= pb_rails("dropdown", props: {
|
|
2
|
+
allow_option_creation: true,
|
|
3
|
+
label: "Grid Alignment Group",
|
|
4
|
+
margin_bottom: "md",
|
|
5
|
+
name: "grid_alignment_group",
|
|
6
|
+
options: [{ id: "existing", label: "Existing group", value: "existing" }],
|
|
7
|
+
}) %>
|
|
8
|
+
|
|
9
|
+
<%= pb_rails("dropdown", props: {
|
|
10
|
+
allow_option_creation: true,
|
|
11
|
+
label: "Home IDs",
|
|
12
|
+
multi_select: true,
|
|
13
|
+
name: "home_ids[]",
|
|
14
|
+
options: [{ id: "existing", label: "Existing group", value: "existing" }],
|
|
15
|
+
placeholder: "Type a home ID and press Enter",
|
|
16
|
+
}) %>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import Dropdown from '../_dropdown'
|
|
3
|
+
|
|
4
|
+
const DropdownOptionCreation = () => {
|
|
5
|
+
const options = [{ id: 'existing', label: 'Existing tag', value: 'existing' }]
|
|
6
|
+
|
|
7
|
+
return (
|
|
8
|
+
<>
|
|
9
|
+
<Dropdown
|
|
10
|
+
allowOptionCreation
|
|
11
|
+
label="Grid Alignment Group"
|
|
12
|
+
marginBottom="md"
|
|
13
|
+
name="gridAlignmentGroup"
|
|
14
|
+
options={options}
|
|
15
|
+
/>
|
|
16
|
+
<Dropdown
|
|
17
|
+
allowOptionCreation
|
|
18
|
+
label="Tags"
|
|
19
|
+
multiSelect
|
|
20
|
+
name="tags[]"
|
|
21
|
+
options={options}
|
|
22
|
+
/>
|
|
23
|
+
</>
|
|
24
|
+
)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export default DropdownOptionCreation
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
Enable `allow_option_creation: true` to select text that is not already an option. This also enables autocomplete on the default trigger. With custom subcomponents, provide an autocomplete trigger or a searchable container. Creation supports single and multiple selections, including async search, and is ignored for the quickpick variant.
|
|
2
|
+
|
|
3
|
+
Type a new value and click the `Create "…"` row, or use the arrow keys and Enter. A non-empty search focuses the Create row when it is available; arrow keys can choose another option. Creation trims surrounding whitespace, rejects blank text, and suppresses case-insensitive matches against existing or selected labels, values, and IDs. An exact ID or value match keeps the existing option visible and selectable even when its label does not contain the query. The selected object is `{ id: text, label: text, value: text, __isNew__: true }`. The Create label is presentation only.
|
|
4
|
+
|
|
5
|
+
Created selections emit the normal `pb:dropdown:selected` event with the option object, or an array for `multi_select: true`. Rails retains its existing native form support. Multi-select fields submit individual selected values in selection order, and the field name receives a `[]` suffix only if it does not already have one. While selections exist, the empty base input is disabled so it does not add a blank array entry. Clearing the selection restores the base input for required validation; disabled Dropdowns stay excluded from submission.
|
|
6
|
+
|
|
7
|
+
For async usage, combine `allow_option_creation: true` with `async: true` and handle the existing `pb:dropdown:search` event using its `setResults` and `setError` callbacks. Creation is hidden during pending searches and errors, then offered when settled results have no exact match. Creation can be offered below `search_term_minimum_length`, when no remote search runs.
|
|
8
|
+
|
|
9
|
+
Creation does not persist a server record or append to application-owned options; persist selections through your normal form submission or selection handler.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
Enable `allowOptionCreation` to select text that is not already an option. This also enables autocomplete on the default trigger. Creation supports single and multiple selections, including async search, and is ignored for the quickpick variant.
|
|
2
|
+
|
|
3
|
+
Type a new value and click the `Create "…"` row, or use the arrow keys and Enter. Creation trims surrounding whitespace, rejects blank text, and suppresses case-insensitive matches against existing or selected labels, values, and IDs. An exact ID or value match keeps the existing option visible and selectable even when its label does not contain the query. The selected object is `{ id: text, label: text, value: text, __isNew__: true }`. The Create label is presentation only.
|
|
4
|
+
|
|
5
|
+
Created selections use the normal `onSelect(optionOrArray)` and `onChange({ target: { name, value } })` callbacks. Multiple selections produce an array of option objects. Native form submission additionally requires `enableFormSupport`; option creation alone does not enable it. Creation does not persist a server record or append to application-owned options; persist selections through your normal form submission or selection handler.
|
|
6
|
+
|
|
7
|
+
For async usage, combine `allowOptionCreation` with `async` and your existing `loadOptions`; both Promise and callback loaders are supported. Creation is hidden during pending searches and errors, then offered when settled results have no exact match. Applications that manage fetching themselves must pass `loading` while the request is pending.
|
|
8
|
+
|
|
9
|
+
A non-empty search focuses the Create row when it is available; arrow keys can choose another option. Closing the menu or clearing the query removes automatic option focus. Enter selects a highlighted option, including when the query is blank. Enter with a closed menu, or a blank query and no highlighted option, remains available for normal form submission and does not change the selection.
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
"options": "Options.data"
|
|
6
6
|
},
|
|
7
7
|
"defaults": {
|
|
8
|
+
"allowOptionCreation": false,
|
|
8
9
|
"async": false,
|
|
9
10
|
"autocomplete": false,
|
|
10
11
|
"blankSelection": "",
|
|
@@ -12,13 +13,18 @@
|
|
|
12
13
|
"closeOnClick": "any",
|
|
13
14
|
"constrainHeight": false,
|
|
14
15
|
"disabled": false,
|
|
16
|
+
"enablePillReorder": false,
|
|
17
|
+
"externalDragHandle": false,
|
|
15
18
|
"multiSelect": false,
|
|
16
19
|
"separators": true,
|
|
20
|
+
"showPillIndex": false,
|
|
17
21
|
"variant": "default",
|
|
18
22
|
"rangeEndsToday": false,
|
|
19
23
|
"controlsStartId": "",
|
|
20
24
|
"controlsEndId": "",
|
|
21
25
|
"requiredIndicator": false,
|
|
26
|
+
"required": false,
|
|
27
|
+
"validationMessage": "",
|
|
22
28
|
"dark": false,
|
|
23
29
|
"label": "Select a Country",
|
|
24
30
|
"options": [
|
|
@@ -124,7 +130,11 @@
|
|
|
124
130
|
"closeOnClick",
|
|
125
131
|
"constrainHeight",
|
|
126
132
|
"isClosed",
|
|
127
|
-
"
|
|
133
|
+
"enableFormSupport",
|
|
134
|
+
"requiredIndicator",
|
|
135
|
+
"required",
|
|
136
|
+
"validationMessage",
|
|
137
|
+
"allowOptionCreation"
|
|
128
138
|
]
|
|
129
139
|
},
|
|
130
140
|
{
|
|
@@ -141,6 +151,9 @@
|
|
|
141
151
|
"props": [
|
|
142
152
|
"separators",
|
|
143
153
|
"formPillProps",
|
|
154
|
+
"enablePillReorder",
|
|
155
|
+
"externalDragHandle",
|
|
156
|
+
"showPillIndex",
|
|
144
157
|
"activeStyle",
|
|
145
158
|
"dark",
|
|
146
159
|
"disabled"
|
|
@@ -155,6 +168,15 @@
|
|
|
155
168
|
}
|
|
156
169
|
],
|
|
157
170
|
"presets": [
|
|
171
|
+
{
|
|
172
|
+
"name": "Create tags",
|
|
173
|
+
"props": {
|
|
174
|
+
"allowOptionCreation": true,
|
|
175
|
+
"multiSelect": true,
|
|
176
|
+
"name": "tags[]",
|
|
177
|
+
"label": "Tags"
|
|
178
|
+
}
|
|
179
|
+
},
|
|
158
180
|
{
|
|
159
181
|
"name": "Async search",
|
|
160
182
|
"structureMode": "async",
|
|
@@ -339,6 +361,15 @@
|
|
|
339
361
|
"formPillProps": {
|
|
340
362
|
"requires": "multiSelect"
|
|
341
363
|
},
|
|
364
|
+
"enablePillReorder": {
|
|
365
|
+
"requires": "multiSelect"
|
|
366
|
+
},
|
|
367
|
+
"externalDragHandle": {
|
|
368
|
+
"requires": "enablePillReorder"
|
|
369
|
+
},
|
|
370
|
+
"showPillIndex": {
|
|
371
|
+
"requires": "multiSelect"
|
|
372
|
+
},
|
|
342
373
|
"rangeEndsToday": {
|
|
343
374
|
"requires": {
|
|
344
375
|
"variant": "quickpick"
|
|
@@ -389,7 +420,7 @@
|
|
|
389
420
|
"when": {
|
|
390
421
|
"multiSelect": true
|
|
391
422
|
},
|
|
392
|
-
"message": "Multi-select returns arrays of selected options. Use formPillProps to adjust the selected-pill appearance and clearable to let users reset the selection.",
|
|
423
|
+
"message": "Multi-select returns arrays of selected options. Use formPillProps to adjust the selected-pill appearance and clearable to let users reset the selection. Opt into enablePillReorder to drag pills into a new priority order.",
|
|
393
424
|
"type": "info"
|
|
394
425
|
},
|
|
395
426
|
"autocomplete_info": {
|