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..ffa358c49 100644
--- a/lib/src/Dropdown/Dropdown.tsx
+++ b/lib/src/Dropdown/Dropdown.tsx
@@ -79,14 +79,28 @@ 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: HTMLOptionElement = selectRef.current.options.item(i);
+ return option.disabled ? true : 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 +157,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 {