diff --git a/playbook/app/pb_kits/playbook/pb_dropdown/_dropdown.tsx b/playbook/app/pb_kits/playbook/pb_dropdown/_dropdown.tsx index cada93d11f..5940e22e29 100644 --- a/playbook/app/pb_kits/playbook/pb_dropdown/_dropdown.tsx +++ b/playbook/app/pb_kits/playbook/pb_dropdown/_dropdown.tsx @@ -22,6 +22,7 @@ import { type DropdownProps = { aria?: { [key: string]: string }; autocomplete?: boolean; + blankSelection?: string; children?: React.ReactChild[] | React.ReactChild | React.ReactElement[]; className?: string; dark?: boolean; @@ -41,6 +42,7 @@ const Dropdown = (props: DropdownProps) => { const { aria = {}, autocomplete = false, + blankSelection = '', children, className, dark = false, @@ -118,11 +120,12 @@ const Dropdown = (props: DropdownProps) => { setIsDropDownClosed(isClosed) }, [isClosed]) - const filteredOptions = options?.filter((option: GenericObject) => { + const blankSelectionOption: GenericObject = blankSelection ? [{ label: blankSelection, value: "" }] : []; + const optionsWithBlankSelection = blankSelectionOption.concat(options); + const filteredOptions = optionsWithBlankSelection?.filter((option: GenericObject) => { const label = typeof option.label === 'string' ? option.label.toLowerCase() : option.label; return String(label).toLowerCase().includes(filterItem.toLowerCase()); - } - ); + }); // For keyboard accessibility: Set focus within dropdown to selected item if it exists useEffect(() => { @@ -196,7 +199,7 @@ const Dropdown = (props: DropdownProps) => { inputWrapperRef, isDropDownClosed, isInputFocused, - options, + optionsWithBlankSelection, selected, setFocusedOptionIndex, setIsDropDownClosed, @@ -235,8 +238,8 @@ const Dropdown = (props: DropdownProps) => { <> - {options && - options?.map((option: GenericObject) => ( + {optionsWithBlankSelection && + optionsWithBlankSelection?.map((option: GenericObject) => ( diff --git a/playbook/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_blank_selection.html.erb b/playbook/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_blank_selection.html.erb new file mode 100644 index 0000000000..e3405df84a --- /dev/null +++ b/playbook/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_blank_selection.html.erb @@ -0,0 +1,10 @@ +<% + options = [ + { label: 'United States', value: 'United States', id: 'us' }, + { label: 'Canada', value: 'Canada', id: 'ca' }, + { label: 'Pakistan', value: 'Pakistan', id: 'pk' }, + ] + +%> + +<%= pb_rails("dropdown", props: { blank_selection: "Select One...", options: options }) %> diff --git a/playbook/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_blank_selection.jsx b/playbook/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_blank_selection.jsx new file mode 100644 index 0000000000..a73a5e8c6a --- /dev/null +++ b/playbook/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_blank_selection.jsx @@ -0,0 +1,31 @@ +import React from 'react' +import { Dropdown } from '../../' + +const DropdownBlankSelection = (props) => { + const options = [ + { + label: "United States", + value: "United States", + }, + { + label: "Canada", + value: "Canada", + }, + { + label: "Pakistan", + value: "Pakistan", + } + ]; + + return ( + <> + + + ) +} + +export default DropdownBlankSelection diff --git a/playbook/app/pb_kits/playbook/pb_dropdown/docs/example.yml b/playbook/app/pb_kits/playbook/pb_dropdown/docs/example.yml index 267b06e0fc..5f4d445870 100644 --- a/playbook/app/pb_kits/playbook/pb_dropdown/docs/example.yml +++ b/playbook/app/pb_kits/playbook/pb_dropdown/docs/example.yml @@ -9,6 +9,7 @@ examples: - dropdown_with_custom_padding: Custom Option Padding - dropdown_error: Dropdown with Error - dropdown_default_value: Default Value + - dropdown_blank_selection: Blank Selection react: - dropdown_default: Default @@ -20,6 +21,7 @@ examples: - dropdown_with_custom_padding: Custom Option Padding - dropdown_error: Dropdown with Error - dropdown_default_value: Default Value + - dropdown_blank_selection: Blank Selection # - dropdown_with_autocomplete: Autocomplete # - dropdown_with_autocomplete_and_custom_display: Autocomplete with Custom Display # - dropdown_with_external_control: useDropdown Hook diff --git a/playbook/app/pb_kits/playbook/pb_dropdown/docs/index.js b/playbook/app/pb_kits/playbook/pb_dropdown/docs/index.js index 451a930ef9..92feffe44a 100644 --- a/playbook/app/pb_kits/playbook/pb_dropdown/docs/index.js +++ b/playbook/app/pb_kits/playbook/pb_dropdown/docs/index.js @@ -10,4 +10,5 @@ export { default as DropdownWithExternalControl } from './_dropdown_with_externa export { default as DropdownWithHook } from './_dropdown_with_hook.jsx' export { default as DropdownSubcomponentStructure } from './_dropdown_subcomponent_structure.jsx' export { default as DropdownError } from './_dropdown_error.jsx' -export { default as DropdownDefaultValue } from './_dropdown_default_value.jsx' \ No newline at end of file +export { default as DropdownDefaultValue } from './_dropdown_default_value.jsx' +export { default as DropdownBlankSelection } from './_dropdown_blank_selection.jsx' diff --git a/playbook/app/pb_kits/playbook/pb_dropdown/dropdown.html.erb b/playbook/app/pb_kits/playbook/pb_dropdown/dropdown.html.erb index 9586c12c5d..fc2a261045 100644 --- a/playbook/app/pb_kits/playbook/pb_dropdown/dropdown.html.erb +++ b/playbook/app/pb_kits/playbook/pb_dropdown/dropdown.html.erb @@ -17,8 +17,8 @@ <% else %> <%= pb_rails("dropdown/dropdown_trigger") %> <%= pb_rails("dropdown/dropdown_container") do %> - <% if object.options.present? %> - <% object.options.each do |option| %> + <% if options_with_blank.present? %> + <% options_with_blank.each do |option| %> <%= pb_rails("dropdown/dropdown_option", props: {option: option}) %> <% end %> <% end %> diff --git a/playbook/app/pb_kits/playbook/pb_dropdown/dropdown.rb b/playbook/app/pb_kits/playbook/pb_dropdown/dropdown.rb index 48a6988b2b..d390b7e015 100644 --- a/playbook/app/pb_kits/playbook/pb_dropdown/dropdown.rb +++ b/playbook/app/pb_kits/playbook/pb_dropdown/dropdown.rb @@ -11,6 +11,8 @@ class Dropdown < Playbook::KitBase prop :required, type: Playbook::Props::Boolean, default: false prop :default_value + prop :blank_selection, type: Playbook::Props::String, + default: "" def data Hash(prop(:data)).merge(pb_dropdown: true) @@ -29,6 +31,10 @@ def error_class def input_default_value default_value.present? ? default_value.transform_keys(&:to_s) : "" end + + def options_with_blank + blank_selection.present? ? [{ id: "", value: "", label: blank_selection }] + options : options + end end end end diff --git a/playbook/app/pb_kits/playbook/pb_dropdown/dropdown_trigger.rb b/playbook/app/pb_kits/playbook/pb_dropdown/dropdown_trigger.rb index 84fb4a2c8f..e34e562dbf 100644 --- a/playbook/app/pb_kits/playbook/pb_dropdown/dropdown_trigger.rb +++ b/playbook/app/pb_kits/playbook/pb_dropdown/dropdown_trigger.rb @@ -11,7 +11,7 @@ class DropdownTrigger < Playbook::KitBase prop :custom_display def data - Hash(prop(:data)).merge(dropdown_trigger: true) + Hash(prop(:data)).merge(dropdown_trigger: true, dropdown_placeholder: default_display_placeholder) end def classname diff --git a/playbook/app/pb_kits/playbook/pb_dropdown/index.js b/playbook/app/pb_kits/playbook/pb_dropdown/index.js index 6035afa4c5..42219cda92 100644 --- a/playbook/app/pb_kits/playbook/pb_dropdown/index.js +++ b/playbook/app/pb_kits/playbook/pb_dropdown/index.js @@ -9,6 +9,8 @@ const UP_ARROW_SELECTOR = "#dropdown_close_icon"; const OPTION_SELECTOR = "[data-dropdown-option-label]"; const CUSTOM_DISPLAY_SELECTOR = "[data-dropdown-custom-trigger]"; const INPUT_FORM_VALIDATION = "#dropdown-form-validation"; +const DROPDOWN_TRIGGER_DISPLAY = "#dropdown_trigger_display"; +const DROPDOWN_PLACEHOLDER = "[data-dropdown-placeholder]"; export default class PbDropdown extends PbEnhancedElement { static get selector() { @@ -25,6 +27,7 @@ export default class PbDropdown extends PbEnhancedElement { this.bindEventListeners(); this.updateArrowDisplay(false); this.handleFormValidation(); + this.handleFormReset(); } bindEventListeners() { @@ -84,9 +87,7 @@ export default class PbDropdown extends PbEnhancedElement { } onOptionSelected(value, selectedOption) { - const triggerElement = this.element.querySelector( - "#dropdown_trigger_display" - ); + const triggerElement = this.element.querySelector(DROPDOWN_TRIGGER_DISPLAY); const customDisplayElement = this.element.querySelector( "#dropdown_trigger_custom_display" ); @@ -195,13 +196,38 @@ export default class PbDropdown extends PbEnhancedElement { } }); - const triggerElement = this.element.querySelector("#dropdown_trigger_display"); - if (triggerElement) { - triggerElement.textContent = defaultValue.label; - } + this.setTriggerElementText(defaultValue.label); hiddenInput.value = defaultValue.id; inputFormValidation.value = defaultValue.id; } } + + handleFormReset() { + const form = this.element.closest("form"); + + if (form) { + form.addEventListener("reset", () => { + this.resetDropdownValue(); + }); + } + } + + resetDropdownValue() { + const hiddenInput = this.element.querySelector("#dropdown-selected-option"); + const inputFormValidation = this.element.querySelector(INPUT_FORM_VALIDATION); + + hiddenInput.value = ""; + inputFormValidation.value = ""; + + const defaultPlaceholder = this.element.querySelector(DROPDOWN_PLACEHOLDER); + this.setTriggerElementText(defaultPlaceholder.dataset.dropdownPlaceholder); + } + + setTriggerElementText(text) { + const triggerElement = this.element.querySelector(DROPDOWN_TRIGGER_DISPLAY); + if (triggerElement) { + triggerElement.textContent = text; + } + } }