diff --git a/playbook/app/pb_kits/playbook/pb_multi_level_select/_multi_level_select.tsx b/playbook/app/pb_kits/playbook/pb_multi_level_select/_multi_level_select.tsx index 911d22ab02..07cd852766 100644 --- a/playbook/app/pb_kits/playbook/pb_multi_level_select/_multi_level_select.tsx +++ b/playbook/app/pb_kits/playbook/pb_multi_level_select/_multi_level_select.tsx @@ -1,14 +1,17 @@ -import React, { useState, useEffect, useRef } from "react" -import classnames from "classnames" -import { globalProps, GlobalProps } from "../utilities/globalProps" -import { buildAriaProps, buildCss, buildDataProps, buildHtmlProps } from "../utilities/props" -import Checkbox from "../pb_checkbox/_checkbox" -import Radio from "../pb_radio/_radio" -import Body from "../pb_body/_body" -import Icon from "../pb_icon/_icon" -import FormPill from "../pb_form_pill/_form_pill" -import CircleIconButton from "../pb_circle_icon_button/_circle_icon_button" -import { cloneDeep } from "lodash" +import React, { useState, useEffect, useRef } from "react"; +import classnames from "classnames"; +import { globalProps, GlobalProps } from "../utilities/globalProps"; +import { + buildAriaProps, + buildCss, + buildDataProps, + buildHtmlProps, +} from "../utilities/props"; +import Icon from "../pb_icon/_icon"; +import FormPill from "../pb_form_pill/_form_pill"; +import { cloneDeep } from "lodash"; +import MultiLevelSelectOptions from "./multi_level_select_options"; +import MultiLevelSelectContext from "./context"; import { getAncestorsOfUnchecked, @@ -18,7 +21,7 @@ import { getDefaultCheckedItems, recursiveCheckParent, getExpandedItems, -} from "./_helper_functions" +} from "./_helper_functions"; type MultiLevelSelectProps = { aria?: { [key: string]: string } @@ -30,9 +33,9 @@ type MultiLevelSelectProps = { inputName?: string name?: string returnAllSelected?: boolean - treeData?: { [key: string]: string }[] + treeData?: { [key: string]: string; }[] | any onSelect?: (prop: { [key: string]: any }) => void - selectedIds?: string[] + selectedIds?: string[] | any variant?: "multi" | "single" pillColor?: "primary" | "neutral" | "success" | "warning" | "error" | "info" | "data_1" | "data_2" | "data_3" | "data_4" | "data_5" | "data_6" | "data_7" | "data_8" | "windows" | "siding" | "roofing" | "doors" | "gutters" | "solar" | "insulation" | "accessories", } & GlobalProps @@ -52,126 +55,132 @@ const MultiLevelSelect = (props: MultiLevelSelectProps) => { onSelect = () => null, selectedIds, variant = "multi", + children, pillColor = "primary" } = props - const ariaProps = buildAriaProps(aria) - const dataProps = buildDataProps(data) - const htmlProps = buildHtmlProps(htmlOptions) + const ariaProps = buildAriaProps(aria); + const dataProps = buildDataProps(data); + const htmlProps = buildHtmlProps(htmlOptions); const classes = classnames( buildCss("pb_multi_level_select"), globalProps(props), className - ) + ); - const dropdownRef = useRef(null) + const dropdownRef = useRef(null); // State for whether dropdown is open or closed - const [isDropdownClosed, setIsDropdownClosed] = useState(true) + const [isDropdownClosed, setIsDropdownClosed] = useState(true); // State from onChange for textinput, to use for filtering to create typeahead - const [filterItem, setFilterItem] = useState("") + const [filterItem, setFilterItem] = useState(""); // FormattedData with checked and parent_id added - const [formattedData, setFormattedData] = useState([]) + const [formattedData, setFormattedData] = useState([]); // State for the return of returnAllSelected - const [returnedArray, setReturnedArray] = useState([]) + const [returnedArray, setReturnedArray] = useState([]); // State for default return - const [defaultReturn, setDefaultReturn] = useState([]) + const [defaultReturn, setDefaultReturn] = useState([]); // Get expanded items from treeData - const initialExpandedItems = getExpandedItems(treeData, selectedIds) + const initialExpandedItems = getExpandedItems(treeData, selectedIds); // Initialize state with expanded items - const [expanded, setExpanded] = useState(initialExpandedItems) + const [expanded, setExpanded] = useState(initialExpandedItems); // Single Select specific state const [singleSelectedItem, setSingleSelectedItem] = useState({ id: [], value: "", - item: [] - }) + item: [], + }); const arrowDownElementId = `arrow_down_${id}` const arrowUpElementId = `arrow_up_${id}` const modifyRecursive = (tree: { [key: string]: any }[], check: boolean) => { if (!Array.isArray(tree)) { - return + return; } return tree.map((item: { [key: string]: any }) => { - item.checked = check - item.children = modifyRecursive(item.children, check) - return item - }) - } + item.checked = check; + item.children = modifyRecursive(item.children, check); + return item; + }); + }; - // Function to map over data and add parent_id + depth property to each item + // Function to map over data and add parent_id + depth property to each item const addCheckedAndParentProperty = ( treeData: { [key: string]: any }[], selectedIds: string[], - parent_id: string = null, - depth = 0, + parent_id: string | null = null, + depth = 0 ) => { if (!Array.isArray(treeData)) { - return + return; } return treeData.map((item: { [key: string]: any } | any) => { const newItem = { ...item, - checked: Boolean(selectedIds && selectedIds.length && selectedIds.includes(item.id)), + checked: Boolean( + selectedIds && selectedIds.length && selectedIds.includes(item.id) + ), parent_id, depth, - } + }; if (newItem.children && newItem.children.length > 0) { const children = item.checked && !returnAllSelected ? modifyRecursive(item.children, true) - : item.children + : item.children; newItem.children = addCheckedAndParentProperty( children, selectedIds, newItem.id, depth + 1 - ) + ); } - return newItem - }) - } + return newItem; + }); + }; useEffect(() => { const formattedData = addCheckedAndParentProperty( treeData, variant === "single" ? [selectedIds?.[0]] : selectedIds - ) + ); - setFormattedData(formattedData) + setFormattedData(formattedData); if (variant === "single") { // No selectedIds, reset state if (selectedIds?.length === 0 || !selectedIds?.length) { - setSingleSelectedItem({ id: [], value: "", item: []}) + setSingleSelectedItem({ id: [], value: "", item: [] }); } else { // If there is a selectedId but no current item, set the selectedItem if (selectedIds?.length !== 0 && !singleSelectedItem.value) { - const selectedItem = filterFormattedDataById(formattedData, selectedIds[0]) + const selectedItem = filterFormattedDataById( + formattedData, + selectedIds[0] + ); if (!selectedItem.length) { - setSingleSelectedItem({ id: [], value: "", item: []}) + setSingleSelectedItem({ id: [], value: "", item: [] }); } else { - const { id, value } = selectedItem[0] - setSingleSelectedItem({ id: [id], value, item: selectedItem}) + const { id, value } = selectedItem[0]; + setSingleSelectedItem({ id: [id], value, item: selectedItem }); } } } } - }, [treeData, selectedIds]) + }, [treeData, selectedIds]); useEffect(() => { if (returnAllSelected) { - setReturnedArray(getCheckedItems(formattedData)) + setReturnedArray(getCheckedItems(formattedData)); } else if (variant === "single") { - setDefaultReturn(singleSelectedItem.item) + setDefaultReturn(singleSelectedItem.item); } else { - setDefaultReturn(getDefaultCheckedItems(formattedData)) + setDefaultReturn(getDefaultCheckedItems(formattedData)); } - }, [formattedData]) + }, [formattedData]); useEffect(() => { // Function to handle clicks outside the dropdown @@ -184,16 +193,14 @@ const MultiLevelSelect = (props: MultiLevelSelectProps) => { ) { setIsDropdownClosed(true) } - } + }; // Attach the event listener - window.addEventListener("click", handleClickOutside) + window.addEventListener("click", handleClickOutside); // Clean up the event listener on unmount return () => { - window.removeEventListener("click", handleClickOutside) - } - }, []) - - + window.removeEventListener("click", handleClickOutside); + }; + }, []); // Iterate over tree, find item and set checked or unchecked const modifyValue = ( @@ -202,69 +209,67 @@ const MultiLevelSelect = (props: MultiLevelSelectProps) => { check: boolean ) => { if (!Array.isArray(tree)) { - return + return; } return tree.map((item: any) => { - if (item.id != id) item.children = modifyValue(id, item.children, check) + if (item.id != id) item.children = modifyValue(id, item.children, check); else { - item.checked = check + item.checked = check; if (variant === "single") { // Single select: no children should be checked - item.children = modifyRecursive(item.children, !check) + item.children = modifyRecursive(item.children, !check); } else { - item.children = modifyRecursive(item.children, check) + item.children = modifyRecursive(item.children, check); } } - return item - }) - } + return item; + }); + }; // Clone tree, check items + children const checkItem = (item: { [key: string]: any }) => { - const tree = cloneDeep(formattedData) + const tree = cloneDeep(formattedData); if (returnAllSelected) { - return modifyValue(item.id, tree, true) + return modifyValue(item.id, tree, true); } else { - const checkedTree = modifyValue(item.id, tree, true) - return recursiveCheckParent(item, checkedTree) + const checkedTree = modifyValue(item.id, tree, true); + return recursiveCheckParent(item, checkedTree); } - } + }; // Clone tree, uncheck items + children const unCheckItem = (item: { [key: string]: any }) => { - const tree = cloneDeep(formattedData) + const tree = cloneDeep(formattedData); if (returnAllSelected) { - return modifyValue(item.id, tree, false) + return modifyValue(item.id, tree, false); } else { - const uncheckedTree = modifyValue(item.id, tree, false) - return getAncestorsOfUnchecked(uncheckedTree, item) + const uncheckedTree = modifyValue(item.id, tree, false); + return getAncestorsOfUnchecked(uncheckedTree, item); } - } + }; // setFormattedData with proper properties const changeItem = (item: { [key: string]: any }, check: boolean) => { - const tree = check ? checkItem(item) : unCheckItem(item) - setFormattedData(tree) - - return tree - } + const tree = check ? checkItem(item) : unCheckItem(item); + setFormattedData(tree); - + return tree; + }; // Click event for x on form pill const handlePillClose = (event: any, clickedItem: { [key: string]: any }) => { // Prevents the dropdown from closing when clicking on the pill - event.stopPropagation() - const updatedTree = changeItem(clickedItem, false) + event.stopPropagation(); + const updatedTree = changeItem(clickedItem, false); // Logic for removing items from returnArray or defaultReturn when pills clicked if (returnAllSelected) { - onSelect(getCheckedItems(updatedTree)) + onSelect(getCheckedItems(updatedTree)); } else { - onSelect(getDefaultCheckedItems(updatedTree)) + onSelect(getDefaultCheckedItems(updatedTree)); } - } + }; // Handle click on input wrapper(entire div with pills, typeahead, etc) so it doesn't close when input or form pill is clicked const handleInputWrapperClick = (e: any) => { @@ -272,163 +277,114 @@ const MultiLevelSelect = (props: MultiLevelSelectProps) => { e.target.id === "multiselect_input" || e.target.classList.contains("pb_form_pill_tag") ) { - return + return; } - setIsDropdownClosed(!isDropdownClosed) - } + setIsDropdownClosed(!isDropdownClosed); + }; // Main function to handle any click inside dropdown const handledropdownItemClick = (e: any, check: boolean) => { - const clickedItem = e.target.parentNode.id + const clickedItem = e.target.parentNode.id; // Setting filterItem to "" will clear textinput and clear typeahead - setFilterItem("") + setFilterItem(""); - const filtered = filterFormattedDataById(formattedData, clickedItem) - const updatedTree = changeItem(filtered[0], check) + const filtered = filterFormattedDataById(formattedData, clickedItem); + const updatedTree = changeItem(filtered[0], check); if (returnAllSelected) { - onSelect(getCheckedItems(updatedTree)) + onSelect(getCheckedItems(updatedTree)); } else { - onSelect(getDefaultCheckedItems(updatedTree)) + onSelect(getDefaultCheckedItems(updatedTree)); } - } + }; // Single select - const handleRadioButtonClick = ( - e: React.ChangeEvent, - ) => { - const { id, value: inputText } = e.target + const handleRadioButtonClick = (e: React.ChangeEvent) => { + const { id, value: inputText } = e.target; // The radio button needs a unique ID, this grabs the ID before the hyphen - const selectedItemID = id.match(/^[^-]*/)[0] + const selectedItemID = id.match(/^[^-]*/)[0]; // Reset tree checked state, triggering useEffect - const treeWithNoSelections = modifyRecursive(formattedData, false) + const treeWithNoSelections = modifyRecursive(formattedData, false); // Update tree with single selection - const treeWithSelectedItem = modifyValue(selectedItemID, treeWithNoSelections, true) - const selectedItem = filterFormattedDataById(treeWithSelectedItem, selectedItemID) - - setFormattedData(treeWithSelectedItem) - setSingleSelectedItem({id: [selectedItemID], value: inputText, item: selectedItem}) + const treeWithSelectedItem = modifyValue( + selectedItemID, + treeWithNoSelections, + true + ); + const selectedItem = filterFormattedDataById( + treeWithSelectedItem, + selectedItemID + ); + + setFormattedData(treeWithSelectedItem); + setSingleSelectedItem({ + id: [selectedItemID], + value: inputText, + item: selectedItem, + }); // Reset the filter to always display dropdown options on click - setFilterItem("") - setIsDropdownClosed(true) + setFilterItem(""); + setIsDropdownClosed(true); - onSelect(selectedItem) + onSelect(selectedItem); }; // Single select: reset the tree state upon typing const handleRadioInputChange = (inputText: string) => { - modifyRecursive(formattedData, false) - setDefaultReturn([]) - setSingleSelectedItem({id: [], value: inputText, item: []}) - setFilterItem(inputText) + modifyRecursive(formattedData, false); + setDefaultReturn([]); + setSingleSelectedItem({ id: [], value: inputText, item: [] }); + setFilterItem(inputText); }; - const isTreeRowExpanded = (item: any) => expanded.indexOf(item.id) > -1 + const isTreeRowExpanded = (item: any) => expanded.indexOf(item.id) > -1; // Handle click on chevron toggles in dropdown const handleToggleClick = (id: string, event: React.MouseEvent) => { - event.stopPropagation() - const clickedItem = filterFormattedDataById(formattedData, id) + event.stopPropagation(); + const clickedItem = filterFormattedDataById(formattedData, id); if (clickedItem) { - let expandedArray = [...expanded] - const itemExpanded = isTreeRowExpanded(clickedItem[0]) + let expandedArray = [...expanded]; + const itemExpanded = isTreeRowExpanded(clickedItem[0]); if (itemExpanded) - expandedArray = expandedArray.filter((i) => i != clickedItem[0].id) - else expandedArray.push(clickedItem[0].id) + expandedArray = expandedArray.filter((i) => i != clickedItem[0].id); + else expandedArray.push(clickedItem[0].id); - setExpanded(expandedArray) + setExpanded(expandedArray); } - } + }; const itemsSelectedLength = () => { - let items + let items; if (returnAllSelected && returnedArray && returnedArray.length) { - items = returnedArray.length + items = returnedArray.length; } else if (!returnAllSelected && defaultReturn && defaultReturn.length) { - items = defaultReturn.length + items = defaultReturn.length; } - return items - } + return items; + }; // Rendering formattedData to UI based on typeahead - const renderNestedOptions = (items: { [key: string]: any }[]) => { - return ( - - ) - } + const renderNestedOptions = (items: { [key: string]: string; }[] | any ) => { + const hasOptionsChild = React.Children.toArray(props.children).some( + (child: any) => child.type === MultiLevelSelect.Options + ); + + if (hasOptionsChild) { + return React.Children.map(props.children, (child) => { + if (child.type === MultiLevelSelect.Options) { + return React.cloneElement(child, { items }); + } + return null; + }); + } else { + // If no children, use the default rendering + return ( + + ); + } + }; + return (
{ className={classes} id={id} > -
+
-
@@ -509,15 +473,17 @@ const MultiLevelSelect = (props: MultiLevelSelectProps) => { { + onChange={(e) => { variant === "single" ? handleRadioInputChange(e.target.value) - : setFilterItem(e.target.value) + : setFilterItem(e.target.value); }} onClick={() => setIsDropdownClosed(false)} placeholder={ inputDisplay === "none" && itemsSelectedLength() - ? `${itemsSelectedLength()} ${itemsSelectedLength() === 1 ? "item" : "items"} selected` + ? `${itemsSelectedLength()} ${ + itemsSelectedLength() === 1 ? "item" : "items" + } selected` : "Start typing..." } value={singleSelectedItem.value || filterItem} @@ -546,15 +512,16 @@ const MultiLevelSelect = (props: MultiLevelSelectProps) => {
- {renderNestedOptions( - filterItem - ? findByFilter(formattedData, filterItem) - : formattedData - )} + {renderNestedOptions( + filterItem ? findByFilter(formattedData, filterItem) : formattedData + )}
+
- ) -} + ); +}; + +MultiLevelSelect.Options = MultiLevelSelectOptions; -export default MultiLevelSelect \ No newline at end of file +export default MultiLevelSelect; diff --git a/playbook/app/pb_kits/playbook/pb_multi_level_select/context/index.tsx b/playbook/app/pb_kits/playbook/pb_multi_level_select/context/index.tsx new file mode 100644 index 0000000000..b974b5ef6b --- /dev/null +++ b/playbook/app/pb_kits/playbook/pb_multi_level_select/context/index.tsx @@ -0,0 +1,5 @@ +import { createContext } from "react"; + +const MultiLevelSelectContext = createContext({}); + +export default MultiLevelSelectContext; \ No newline at end of file diff --git a/playbook/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_default.jsx b/playbook/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_default.jsx index e364900c70..cb2be03263 100644 --- a/playbook/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_default.jsx +++ b/playbook/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_default.jsx @@ -87,4 +87,4 @@ const MultiLevelSelectDefault = (props) => { ) }; -export default MultiLevelSelectDefault; +export default MultiLevelSelectDefault; \ No newline at end of file diff --git a/playbook/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_with_children.jsx b/playbook/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_with_children.jsx new file mode 100644 index 0000000000..8d1f7d4b4a --- /dev/null +++ b/playbook/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_with_children.jsx @@ -0,0 +1,105 @@ +import React from "react"; +import MultiLevelSelect from "../_multi_level_select"; +import Badge from "../../pb_badge/_badge"; + +const treeData = [ + { + label: "Power Home Remodeling", + value: "Power Home Remodeling", + id: "powerhome1", + expanded: true, + children: [ + { + label: "People", + value: "People", + id: "people1", + expanded: true, + status: "active", + children: [ + { + label: "Talent Acquisition", + value: "Talent Acquisition", + id: "talent1", + }, + { + label: "Business Affairs", + value: "Business Affairs", + id: "business1", + status: "active", + variant: "primary", + + children: [ + { + label: "Initiatives", + value: "Initiatives", + id: "initiative1", + }, + { + label: "Learning & Development", + value: "Learning & Development", + id: "development1", + status: "Inactive", + }, + ], + }, + { + label: "People Experience", + value: "People Experience", + id: "experience1", + }, + ], + }, + { + label: "Contact Center", + value: "Contact Center", + id: "contact1", + status: "Inactive", + variant: "error", + children: [ + { + label: "Appointment Management", + value: "Appointment Management", + id: "appointment1", + }, + { + label: "Customer Service", + value: "Customer Service", + id: "customer1", + }, + { + label: "Energy", + value: "Energy", + id: "energy1", + }, + ], + }, + ], + }, +]; + +const MultiLevelSelectWithChildren = (props) => { + return ( +
+ + console.log("Selected Items", selectedNodes) + } + treeData={treeData} + {...props} + > + + {(item) => ( + + )} + + +
+ ); +}; + +export default MultiLevelSelectWithChildren; diff --git a/playbook/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_with_children.md b/playbook/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_with_children.md new file mode 100644 index 0000000000..d0aeb8d340 --- /dev/null +++ b/playbook/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_with_children.md @@ -0,0 +1 @@ +The MultiLevelSelect also provides a subcomponent structure which can be used to render children to the right of the Checkboxes and their labels. As seen in the code snippet below, these children have access to the current item being iterated over which can be used for conditional rendering. diff --git a/playbook/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_with_children_with_radios.jsx b/playbook/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_with_children_with_radios.jsx new file mode 100644 index 0000000000..a6c03f7859 --- /dev/null +++ b/playbook/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_with_children_with_radios.jsx @@ -0,0 +1,106 @@ +import React from "react"; +import MultiLevelSelect from "../_multi_level_select"; +import Badge from "../../pb_badge/_badge"; + +const treeData = [ + { + label: "Power Home Remodeling", + value: "Power Home Remodeling", + id: "powerhome1", + expanded: true, + children: [ + { + label: "People", + value: "People", + id: "people1", + expanded: true, + status: "active", + children: [ + { + label: "Talent Acquisition", + value: "Talent Acquisition", + id: "talent1", + }, + { + label: "Business Affairs", + value: "Business Affairs", + id: "business1", + status: "active", + variant: "primary", + + children: [ + { + label: "Initiatives", + value: "Initiatives", + id: "initiative1", + }, + { + label: "Learning & Development", + value: "Learning & Development", + id: "development1", + status: "Inactive", + }, + ], + }, + { + label: "People Experience", + value: "People Experience", + id: "experience1", + }, + ], + }, + { + label: "Contact Center", + value: "Contact Center", + id: "contact1", + status: "Inactive", + variant: "error", + children: [ + { + label: "Appointment Management", + value: "Appointment Management", + id: "appointment1", + }, + { + label: "Customer Service", + value: "Customer Service", + id: "customer1", + }, + { + label: "Energy", + value: "Energy", + id: "energy1", + }, + ], + }, + ], + }, +]; + +const MultiLevelSelectWithChildrenWithRadios = (props) => { + return ( +
+ + console.log("Selected Items", selectedNodes) + } + treeData={treeData} + variant="single" + {...props} + > + + {(item) => ( + + )} + + +
+ ); +}; + +export default MultiLevelSelectWithChildrenWithRadios; diff --git a/playbook/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_with_children_with_radios.md b/playbook/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_with_children_with_radios.md new file mode 100644 index 0000000000..b4f8fbc2a6 --- /dev/null +++ b/playbook/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_with_children_with_radios.md @@ -0,0 +1 @@ +The MultiLevelSelect subcomponent structure is also available in the 'Single Select' variant. In this variant, the children will be rendered to the right of the Radios and their labels. \ No newline at end of file diff --git a/playbook/app/pb_kits/playbook/pb_multi_level_select/docs/example.yml b/playbook/app/pb_kits/playbook/pb_multi_level_select/docs/example.yml index c232dee812..273db8bb68 100644 --- a/playbook/app/pb_kits/playbook/pb_multi_level_select/docs/example.yml +++ b/playbook/app/pb_kits/playbook/pb_multi_level_select/docs/example.yml @@ -15,3 +15,6 @@ examples: - multi_level_select_return_all_selected: Return All Selected - multi_level_select_selected_ids_react: Selected Ids - multi_level_select_color: With Pills (Custom Color) + - multi_level_select_with_children: Checkboxes With Children + - multi_level_select_with_children_with_radios: Single Select With Children + diff --git a/playbook/app/pb_kits/playbook/pb_multi_level_select/docs/index.js b/playbook/app/pb_kits/playbook/pb_multi_level_select/docs/index.js index 6b8be4ec17..7c3e1fd0fc 100644 --- a/playbook/app/pb_kits/playbook/pb_multi_level_select/docs/index.js +++ b/playbook/app/pb_kits/playbook/pb_multi_level_select/docs/index.js @@ -4,3 +4,5 @@ export { default as MultiLevelSelectSingleChildrenOnly } from './_multi_level_se export { default as MultiLevelSelectReturnAllSelected } from './_multi_level_select_return_all_selected.jsx' export { default as MultiLevelSelectSelectedIdsReact } from "./_multi_level_select_selected_ids_react.jsx" export { default as MultiLevelSelectColor } from './_multi_level_select_color.jsx' +export { default as MultiLevelSelectWithChildren } from './_multi_level_select_with_children.jsx' +export { default as MultiLevelSelectWithChildrenWithRadios } from './_multi_level_select_with_children_with_radios.jsx' diff --git a/playbook/app/pb_kits/playbook/pb_multi_level_select/multi_level_select_options.tsx b/playbook/app/pb_kits/playbook/pb_multi_level_select/multi_level_select_options.tsx new file mode 100644 index 0000000000..c9a0c4e208 --- /dev/null +++ b/playbook/app/pb_kits/playbook/pb_multi_level_select/multi_level_select_options.tsx @@ -0,0 +1,149 @@ +import React, {useContext} from "react"; +import classnames from "classnames"; +import MultiLevelSelectContext from "./context"; +import { globalProps, GlobalProps } from "../utilities/globalProps"; +import { + buildAriaProps, + buildCss, + buildDataProps, + buildHtmlProps, +} from "../utilities/props"; +import Checkbox from "../pb_checkbox/_checkbox"; +import Radio from "../pb_radio/_radio"; +import CircleIconButton from "../pb_circle_icon_button/_circle_icon_button"; +import Body from "../pb_body/_body"; + +type MultiLevelSelectOptionsProps = { + aria?: { [key: string]: string }, + children?: React.ReactNode | ((item: any) => React.ReactNode), + className?: string, + dark?: boolean, + data?: { [key: string]: string }, + htmlOptions?: {[key: string]: string | number | boolean | (() => void)}, +} & GlobalProps; + +const MultiLevelSelectOptions = ({ + children, + items, + ...props +}: MultiLevelSelectOptionsProps) => { +const { + variant, + inputName, + renderNestedOptions, + isTreeRowExpanded, + handleToggleClick, + handleRadioButtonClick, + handledropdownItemClick, + filterItem, +} = useContext(MultiLevelSelectContext) + +const { + aria = {}, + className, + data = {}, + htmlOptions = {}, +} = props; + +const ariaProps = buildAriaProps(aria); +const dataProps = buildDataProps(data); +const htmlProps = buildHtmlProps(htmlOptions); +const classes = classnames( + buildCss("pb_multi_level_select_options"), + globalProps(props), + className +); + + return ( +
    + {Array.isArray(items) && + items.map((item: { [key: string]: any }) => { + return ( +
    +
  • +
    + {!item.parent_id && !item.children ? null : ( +
    + 0 + ? "" + : "toggle_icon" + } + icon={ + isTreeRowExpanded(item) + ? "chevron-down" + : "chevron-right" + } + onClick={(event: React.MouseEvent) => + handleToggleClick(item.id, event) + } + variant="link" + /> +
    + )} + {variant === "single" ? ( + item.hideRadio ? ( + {item.label} + ) : ( + ) => + handleRadioButtonClick(e) + } + padding={item.children ? "none" : "xs"} + type="radio" + value={item.label} + /> + ) + ) : ( + + { + handledropdownItemClick(e, !item.checked); + }} + type="checkbox" + value={item.label} + /> + + )} + {/* Render children next to the checkbox */} + {children && ( + typeof children === "function" ? children(item) : children + )} +
    + {isTreeRowExpanded(item) && + item.children && + item.children.length > 0 && + (variant === "single" || !filterItem) && ( +
    {renderNestedOptions(item.children)}
    + )} +
  • +
    + ); + })} +
+ ); +}; + +export default MultiLevelSelectOptions; \ No newline at end of file