From 38e4ec2d4e7b4335461788ba36f59b8d98bc108a Mon Sep 17 00:00:00 2001 From: Mario Subotic Date: Mon, 5 Apr 2021 14:54:42 +0800 Subject: [PATCH 1/3] fix(dropdown): fix styles for multi select and disabled options fixes styles for multiple select options and disabled options fix #551 --- lib/src/Dropdown/Dropdown.test.tsx | 34 +++++++++++++++++++++++----- lib/src/Dropdown/Dropdown.tsx | 36 +++++++++++++++++++++++------- lib/src/Dropdown/dropdown.scss | 18 ++++++++++----- 3 files changed, 70 insertions(+), 18 deletions(-) diff --git a/lib/src/Dropdown/Dropdown.test.tsx b/lib/src/Dropdown/Dropdown.test.tsx index 3f677ba46..f7e32f490 100644 --- a/lib/src/Dropdown/Dropdown.test.tsx +++ b/lib/src/Dropdown/Dropdown.test.tsx @@ -13,6 +13,9 @@ const testOptions: React.ReactElement[] = [ , + , ]; describe("Component: Dropdown", () => { @@ -60,10 +63,10 @@ describe("Component: Dropdown", () => { render({testOptions}, container); }); - // 3 options + first empty option that is injected - expect(container.querySelector("select").options).toHaveLength(4); + // 4 options + first empty option that is injected + expect(container.querySelector("select").options).toHaveLength(5); expect(document.body.querySelector(".dropdown-menu")).not.toBeNull(); - expect(document.body.querySelectorAll(".custom-control")).toHaveLength(3); + expect(document.body.querySelectorAll(".custom-control")).toHaveLength(4); }); it("Should render grouped options inside", () => { @@ -77,7 +80,7 @@ describe("Component: Dropdown", () => { ); }); - expect(container.querySelector("select").options).toHaveLength(4); + expect(container.querySelector("select").options).toHaveLength(5); expect(container.querySelector("select").querySelector("optgroup")).not.toBeNull(); expect(document.body.querySelector("label.optgroup-label")).not.toBeNull(); expect(document.body.querySelector("label.optgroup-label").textContent).toEqual(optgroupLabel); @@ -93,6 +96,27 @@ describe("Component: Dropdown", () => { expect(container.querySelector("button.dropdown-toggle").firstElementChild.textContent).toEqual(placeholder); }); + it("Should support disabled options", () => { + const placeholder: string = "My placeholder"; + act(() => { + render({testOptions}, container); + }); + expect(container.querySelector("select").options.item(4).disabled).toBeTruthy(); + }); + + it("Should ignore disabled elements when determining if all elements are selected", () => { + const placeholder: string = "My placeholder"; + act(() => { + render( + + {testOptions} + , + container + ); + }); + expect(document.body.querySelector(".select-all .custom-control-input").checked).toBeTruthy(); + }); + it("Should allow padding a dropdown divider", () => { act(() => { render( @@ -115,7 +139,7 @@ describe("Component: Dropdown", () => { const searchField = document.body.querySelector("input[type=search]"); expect(searchField).not.toBeNull(); - expect(document.body.querySelectorAll(".custom-control")).toHaveLength(3); + expect(document.body.querySelectorAll(".custom-control")).toHaveLength(4); act(() => Simulate.change(searchField, { target: { value: "second" } as any })); diff --git a/lib/src/Dropdown/Dropdown.tsx b/lib/src/Dropdown/Dropdown.tsx index acfe37756..b9c967f78 100644 --- a/lib/src/Dropdown/Dropdown.tsx +++ b/lib/src/Dropdown/Dropdown.tsx @@ -79,14 +79,32 @@ export const Dropdown: React.FC = React.forwardRef(({ wrapperProp [isMobile, props.multiple, onMultipleChange] ); - const selectAll = (forceValue?: boolean | React.ChangeEvent) => { - Array.from(selectRef.current.options).forEach((option) => { - option.selected = typeof forceValue === "boolean" ? forceValue : !allSelected; + const selectAll = React.useCallback( + (forceValue?: boolean | React.ChangeEvent) => { + Array.from(selectRef.current.options).forEach((_, i) => { + const option = selectRef.current.options.item(i); + if (!option.disabled) { + option.selected = typeof forceValue === "boolean" ? forceValue : !allSelected; + } else { + option.selected = false; + } + }); + typeof forceValue === "boolean" && (selectRef.current.value = ""); + selectRef.current.dispatchEvent(new Event("change", { bubbles: true })); + props.multiple && onMultipleChange && onMultipleChange(getValueOfMultipleSelect(selectRef.current)); + }, + [allSelected, props.multiple] + ); + + const isAllSelected = (): boolean => { + return Array.from(selectRef.current.options).every((_, i) => { + const option = selectRef.current.options.item(i); + if (option.disabled) { + return true; + } else { + return option.selected; + } }); - typeof forceValue === "boolean" && (selectRef.current.value = ""); - selectRef.current.dispatchEvent(new Event("change", { bubbles: true })); - setAllSelected(!allSelected); - props.multiple && onMultipleChange && onMultipleChange(getValueOfMultipleSelect(selectRef.current)); }; const toggleMenu = React.useCallback( @@ -143,6 +161,7 @@ export const Dropdown: React.FC = React.forwardRef(({ wrapperProp case "option": return filteredBySearch(Child) ? null : ( = React.forwardRef(({ wrapperProp ...React.Children.toArray(Child.props.children).map((groupChild: React.ReactElement) => { return filteredBySearch(groupChild) ? null : ( = React.forwardRef(({ wrapperProp }; React.useEffect(() => { - !isMobile && props.multiple && setAllSelected(Array.from(selectRef.current.options).every((option) => option.selected)); + !isMobile && props.multiple && setAllSelected(isAllSelected()); }, [props.value]); React.useEffect(() => { diff --git a/lib/src/Dropdown/dropdown.scss b/lib/src/Dropdown/dropdown.scss index 25643be89..6475df161 100644 --- a/lib/src/Dropdown/dropdown.scss +++ b/lib/src/Dropdown/dropdown.scss @@ -48,6 +48,8 @@ &::after { filter: grayscale(1); + transition: none; + transform: none; } } } @@ -102,13 +104,12 @@ padding: 0; > input { - &:checked + label:not(:hover) { - background-color: $blue-darker; - color: white; - } - &[type="radio"] { &:checked + label { + &:not(:hover) { + background-color: $blue-darker; + color: white; + } &::before { content: "\2713"; position: absolute; @@ -116,6 +117,13 @@ } } } + + &:disabled + label { + color: $gray-500; + &:hover { + background-color: $gray-300; + } + } } > label { From 766f8c8a7eda538d62083048b5530c8932905ee3 Mon Sep 17 00:00:00 2001 From: Mario Subotic <49055952+mario-subo@users.noreply.github.com> Date: Mon, 5 Apr 2021 15:30:07 +0800 Subject: [PATCH 2/3] Update lib/src/Dropdown/Dropdown.tsx Co-authored-by: Omar Boudfoust --- lib/src/Dropdown/Dropdown.tsx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/lib/src/Dropdown/Dropdown.tsx b/lib/src/Dropdown/Dropdown.tsx index b9c967f78..a2bb02305 100644 --- a/lib/src/Dropdown/Dropdown.tsx +++ b/lib/src/Dropdown/Dropdown.tsx @@ -99,11 +99,7 @@ export const Dropdown: React.FC = React.forwardRef(({ wrapperProp const isAllSelected = (): boolean => { return Array.from(selectRef.current.options).every((_, i) => { const option = selectRef.current.options.item(i); - if (option.disabled) { - return true; - } else { - return option.selected; - } + return option.disabled ? true : option.selected }); }; From 22cb566bc66f0dd7c54c94f2005d87b05e3e23cc Mon Sep 17 00:00:00 2001 From: Mario Subotic Date: Mon, 5 Apr 2021 15:34:39 +0800 Subject: [PATCH 3/3] style(dropdown): adds missing types --- lib/src/Dropdown/Dropdown.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/Dropdown/Dropdown.tsx b/lib/src/Dropdown/Dropdown.tsx index a2bb02305..ffa358c49 100644 --- a/lib/src/Dropdown/Dropdown.tsx +++ b/lib/src/Dropdown/Dropdown.tsx @@ -98,8 +98,8 @@ export const Dropdown: React.FC = React.forwardRef(({ wrapperProp const isAllSelected = (): boolean => { return Array.from(selectRef.current.options).every((_, i) => { - const option = selectRef.current.options.item(i); - return option.disabled ? true : option.selected + const option: HTMLOptionElement = selectRef.current.options.item(i); + return option.disabled ? true : option.selected; }); };