Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 18 additions & 9 deletions playbook/app/pb_kits/playbook/pb_dropdown/_dropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,8 @@ type DropdownProps = {
isClosed?: boolean;
label?: string;
multiSelect?: boolean;
name?: string;
onChange?: (event: { target: { name?: string; value: any } }) => void;
onSelect?: (arg: GenericObject) => null;
options?: GenericObject;
placeholder?: string;
Expand Down Expand Up @@ -156,6 +158,8 @@ let Dropdown = (props: DropdownProps, ref: any): React.ReactElement | null => {
label,
multiSelect = false,
formPillProps,
name,
onChange,
onSelect,
options,
placeholder,
Expand Down Expand Up @@ -414,6 +418,10 @@ let Dropdown = (props: DropdownProps, ref: any): React.ReactElement | null => {
setIsDropDownClosed(false);
};

const handleSelectionChange = (value: any) => {
onSelect && onSelect(value);
onChange && onChange({ target: { name, value } });
};

const handleOptionClick = (clickedItem: GenericObject) => {
if (disabled) return;
Expand All @@ -426,7 +434,7 @@ let Dropdown = (props: DropdownProps, ref: any): React.ReactElement | null => {
const next = exists
? list.filter((option) => option.value !== clickedItem.value)
: [...list, clickedItem];
onSelect && onSelect(next);
handleSelectionChange(next);
return next;
});
setFilterItem("");
Expand All @@ -439,7 +447,7 @@ let Dropdown = (props: DropdownProps, ref: any): React.ReactElement | null => {
if (shouldCloseOnClick) {
setIsDropDownClosed(true);
}
onSelect && onSelect(clickedItem);
handleSelectionChange(clickedItem);

// Sync with DatePickers if this is a quickpick variant
if (variant === "quickpick" && Array.isArray(clickedItem.value)) {
Expand Down Expand Up @@ -468,10 +476,10 @@ let Dropdown = (props: DropdownProps, ref: any): React.ReactElement | null => {
if (disabled) return;
if (multiSelect) {
setSelected([]);
onSelect && onSelect([]);
handleSelectionChange([]);
} else {
setSelected({});
onSelect && onSelect(null);
handleSelectionChange(null);
setFocusedOptionIndex(-1);
setFilterItem("");

Expand Down Expand Up @@ -505,10 +513,10 @@ let Dropdown = (props: DropdownProps, ref: any): React.ReactElement | null => {
clearSelected: () => {
if (multiSelect) {
setSelected([]);
onSelect && onSelect([]);
handleSelectionChange([]);
} else {
setSelected({});
onSelect && onSelect(null);
handleSelectionChange(null);
}
setFilterItem("");
setIsDropDownClosed(true);
Expand All @@ -522,16 +530,16 @@ let Dropdown = (props: DropdownProps, ref: any): React.ReactElement | null => {
clearSelected: () => {
if (multiSelect) {
setSelected([]);
onSelect && onSelect([]);
handleSelectionChange([]);
} else {
setSelected({});
onSelect && onSelect(null);
handleSelectionChange(null);
}
setFilterItem("");
setIsDropDownClosed(true);
},
};
}, [multiSelect, onSelect, setSelected, setFilterItem, setIsDropDownClosed]);
}, [multiSelect, handleSelectionChange, setSelected, setFilterItem, setIsDropDownClosed]);

useImperativeHandle(ref, () => imperativeRef.current);

Expand Down Expand Up @@ -594,6 +602,7 @@ let Dropdown = (props: DropdownProps, ref: any): React.ReactElement | null => {
handleBackspace,
handleChange,
handleOptionClick,
handleSelectionChange,
handleWrapperClick,
inputRef,
inputWrapperRef,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<%= pb_rails("select", props: {
id: "color_context_dropdown",
label: "Choose a Color",
name: "color_name",
options: [
{ value: "red", value_text: "Red" },
{ value: "blue", value_text: "Blue" },
{ value: "green", value_text: "Green" },
],
}) %>

<%= pb_rails("dropdown", props: {
id: "dropdown-dynamic-options",
label: "Pick a Shade",
name: "shade_name",
context_selector: "color_context_dropdown",
options_by_context: {
"red" => [
{ id: "scarlet", label: "Scarlet", value: "scarlet" },
{ id: "mahogany", label: "Mahogany", value: "mahogany" },
{ id: "crimson", label: "Crimson", value: "crimson" },
],
"blue" => [
{ id: "sky", label: "Sky Blue", value: "sky" },
{ id: "cerulean", label: "Cerulean", value: "cerulean" },
{ id: "navy", label: "Navy", value: "navy" },
],
"green" => [
{ id: "emerald", label: "Emerald", value: "emerald" },
{ id: "mint", label: "Mint", value: "mint" },
{ id: "olive", label: "Olive", value: "olive" },
],
},
}) %>
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
You can set up a dropdown to update its options dynamically based on another input. To achieve this:
- Give the dropdown a unique `id` so it can be targeted by events and linked to a controlling input.
- Use `context_selector` to point at the controlling select’s `id`. On connect, and whenever that select’s value changes, the Dropdown reads the current value as its context.
- Use `options_by_context` to pass a hash of option lists. Keys must match the possible values of the controlling select; each key maps to an array of `{ id, label, value }` options that replace the Dropdown’s options for that context.
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<%= pb_rails("select", props: {
id: "color_context_dropdown_autocomplete",
label: "Choose a Color",
name: "color_name_autocomplete",
options: [
{ value: "red", value_text: "Red" },
{ value: "blue", value_text: "Blue" },
{ value: "green", value_text: "Green" },
],
}) %>

<%= pb_rails("dropdown", props: {
id: "dropdown-dynamic-options-autocomplete",
label: "Pick a Shade",
name: "shade_name_autocomplete",
autocomplete: true,
context_selector: "color_context_dropdown_autocomplete",
options_by_context: {
"red" => [
{ id: "scarlet", label: "Scarlet", value: "scarlet" },
{ id: "mahogany", label: "Mahogany", value: "mahogany" },
{ id: "crimson", label: "Crimson", value: "crimson" },
],
"blue" => [
{ id: "sky", label: "Sky Blue", value: "sky" },
{ id: "cerulean", label: "Cerulean", value: "cerulean" },
{ id: "navy", label: "Navy", value: "navy" },
],
"green" => [
{ id: "emerald", label: "Emerald", value: "emerald" },
{ id: "mint", label: "Mint", value: "mint" },
{ id: "olive", label: "Olive", value: "olive" },
],
},
}) %>
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Dynamic options also work with `autocomplete`. Use the same `context_selector` and `options_by_context` setup as Dynamic Options, and set `autocomplete: true` so the Dropdown can be filtered by typing.
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<%= pb_rails("select", props: {
id: "color_context_dropdown_multi",
label: "Choose a Color",
name: "color_name_multi",
options: [
{ value: "red", value_text: "Red" },
{ value: "blue", value_text: "Blue" },
{ value: "green", value_text: "Green" },
],
}) %>

<%= pb_rails("dropdown", props: {
id: "dropdown-dynamic-options-multi",
label: "Pick Shades",
name: "shade_names",
multi_select: true,
context_selector: "color_context_dropdown_multi",
options_by_context: {
"red" => [
{ id: "scarlet", label: "Scarlet", value: "scarlet" },
{ id: "mahogany", label: "Mahogany", value: "mahogany" },
{ id: "crimson", label: "Crimson", value: "crimson" },
],
"blue" => [
{ id: "sky", label: "Sky Blue", value: "sky" },
{ id: "cerulean", label: "Cerulean", value: "cerulean" },
{ id: "navy", label: "Navy", value: "navy" },
],
"green" => [
{ id: "emerald", label: "Emerald", value: "emerald" },
{ id: "mint", label: "Mint", value: "mint" },
{ id: "olive", label: "Olive", value: "olive" },
],
},
}) %>
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Dynamic options also work with `multi_select`. Use the same `context_selector` and `options_by_context` setup as Dynamic Options, and set `multi_select: true` to allow selecting multiple options.
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import React from 'react'

import Dropdown from '../_dropdown'
import Title from '../../pb_title/_title'
import { useForm } from 'react-hook-form'

const options = [
{ label: 'United States', value: 'unitedStates', id: 'us' },
{ label: 'United Kingdom', value: 'unitedKingdom', id: 'gb' },
{ label: 'Canada', value: 'canada', id: 'ca' },
{ label: 'Pakistan', value: 'pakistan', id: 'pk' },
{ label: 'India', value: 'india', id: 'in' },
{ label: 'Australia', value: 'australia', id: 'au' },
{ label: 'New Zealand', value: 'new Zealand', id: 'nz' },
{ label: 'Italy', value: 'italy', id: 'it' },
{ label: 'Spain', value: 'spain', id: 'es' },
]

const DropdownMultiSelectReactHook = (props) => {
const { register, watch } = useForm()

const selectedCountries = watch('countries')

return (
<>
<Dropdown
label="Countries"
multiSelect
options={options}
{...props}
{...register('countries')}
/>
<Title
marginTop="sm"
size={4}
text="Selected Countries"
/>
{selectedCountries && selectedCountries.map(country => (
<p key={country.id}>{`${country.label} - ${country.value}`}</p>
))}
</>
)
}

export default DropdownMultiSelectReactHook
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
You can pass `react-hook-form` props to a multi-select Dropdown. Spread `register` onto Dropdown with `multiSelect` to keep the selected options array in form state.
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import React from 'react'

import Dropdown from '../_dropdown'
import Title from '../../pb_title/_title'
import { useForm } from 'react-hook-form'

const options = [
{ label: 'United States', value: 'unitedStates', id: 'us' },
{ label: 'Canada', value: 'canada', id: 'ca' },
{ label: 'Pakistan', value: 'pakistan', id: 'pk' },
]

const DropdownReactHook = (props) => {
const { register, watch } = useForm()

const selectedCountry = watch('country')

return (
<>
<Dropdown
label="Countries"
options={options}
{...props}
{...register('country')}
/>
<Title
marginTop="sm"
size={4}
text="Selected Country"
/>
<p>{selectedCountry && `${selectedCountry.label} - ${selectedCountry.value}`}</p>
</>
)
}

export default DropdownReactHook
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
You can pass `react-hook-form` props to the Dropdown kit. Spread `register` onto a single-select Dropdown to keep the selected option in form state.
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@
"name": "Content",
"props": [
"label",
"name",
"placeholder",
"options",
"defaultValue",
Expand Down Expand Up @@ -130,6 +131,7 @@
{
"name": "Events",
"props": [
"onChange",
"onSelect"
]
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
"name": "Content",
"props": [
"label",
"name",
"placeholder",
"options",
"defaultValue",
Expand Down Expand Up @@ -111,7 +112,7 @@
},
{
"name": "Events",
"props": ["onSelect"]
"props": ["onChange", "onSelect"]
}
],
"presets": [
Expand Down
6 changes: 6 additions & 0 deletions playbook/app/pb_kits/playbook/pb_dropdown/docs/example.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ examples:
- dropdown_disabled: Disabled Input
- dropdown_grouped_options: Grouped Options
- dropdown_custom_event_type: Custom Event Type
- dropdown_dynamic_options: Dynamic Options
- dropdown_dynamic_options_with_autocomplete: Dynamic Options with Autocomplete
- dropdown_dynamic_options_with_multi_select: Dynamic Options with Multi Select

react:
- dropdown_default: Default
Expand Down Expand Up @@ -81,3 +84,6 @@ examples:
- dropdown_required_indicator: Required Indicator
- dropdown_disabled: Disabled Input
- dropdown_grouped_options: Grouped Options
- dropdown_react_hook: React Hook
- dropdown_multi_select_react_hook: React Hook Multi Select

2 changes: 2 additions & 0 deletions playbook/app/pb_kits/playbook/pb_dropdown/docs/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
export { default as DropdownDefault } from './_dropdown_default.jsx'
export { default as DropdownReactHook } from './_dropdown_react_hook.jsx'
export { default as DropdownMultiSelectReactHook } from './_dropdown_multi_select_react_hook.jsx'
export { default as DropdownWithCustomDisplay } from './_dropdown_with_custom_display.jsx'
export { default as DropdownWithCustomOptions } from './_dropdown_with_custom_options.jsx'
export { default as DropdownWithCustomTrigger } from './_dropdown_with_custom_trigger.jsx'
Expand Down
14 changes: 13 additions & 1 deletion playbook/app/pb_kits/playbook/pb_dropdown/dropdown.rb
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,14 @@ class Dropdown < Playbook::KitBase
default: false
prop :custom_event_type, type: Playbook::Props::String,
default: ""
prop :options_by_context, type: Playbook::Props::HashProp,
default: {}
prop :context_selector, type: Playbook::Props::String,
default: ""
prop :clear_on_context_change, type: Playbook::Props::Boolean,
default: true
prop :options_event_type, type: Playbook::Props::String,
default: ""

def data
Hash(prop(:data)).merge(
Expand All @@ -73,7 +81,11 @@ def data
end_date_id: variant == "quickpick" ? end_date_id : nil,
controls_start_id: variant == "quickpick" && controls_start_id.present? ? controls_start_id : nil,
controls_end_id: variant == "quickpick" && controls_end_id.present? ? controls_end_id : nil,
custom_event_type: custom_event_type.presence
custom_event_type: custom_event_type.presence,
pb_dropdown_options_by_context: options_by_context.present? ? options_by_context.to_json : nil,
pb_dropdown_context_selector: context_selector.presence,
pb_dropdown_clear_on_context_change: clear_on_context_change,
options_event_type: options_event_type.presence
).compact
end

Expand Down
Loading
Loading