diff --git a/ui/CHANGELOG.md b/ui/CHANGELOG.md
index b2947b6d8e..89970264c0 100644
--- a/ui/CHANGELOG.md
+++ b/ui/CHANGELOG.md
@@ -2,6 +2,15 @@
All notable changes to the **Prowler UI** are documented in this file.
+## [1.26.1] (Prowler 5.26.1)
+
+### 🐞 Fixed
+
+- Role form Cancel buttons now return to Roles [(#11125)](https://github.com/prowler-cloud/prowler/pull/11125)
+- Shared select dropdowns stay constrained and scrollable inside modals [(#11125)](https://github.com/prowler-cloud/prowler/pull/11125)
+
+---
+
## [1.26.0] (Prowler v5.26.0)
### 🚀 Added
diff --git a/ui/components/roles/workflow/forms/add-role-form.test.tsx b/ui/components/roles/workflow/forms/add-role-form.test.tsx
index b7686635a4..80a9c523b3 100644
--- a/ui/components/roles/workflow/forms/add-role-form.test.tsx
+++ b/ui/components/roles/workflow/forms/add-role-form.test.tsx
@@ -1,10 +1,15 @@
import { render, screen } from "@testing-library/react";
+import userEvent from "@testing-library/user-event";
import { afterEach, describe, expect, it, vi } from "vitest";
import { AddRoleForm } from "./add-role-form";
+const routerMocks = vi.hoisted(() => ({
+ push: vi.fn(),
+}));
+
vi.mock("next/navigation", () => ({
- useRouter: () => ({ push: vi.fn() }),
+ useRouter: () => routerMocks,
}));
vi.mock("@/actions/roles/roles", () => ({
@@ -73,6 +78,7 @@ vi.mock("@/components/ui", () => ({
describe("AddRoleForm", () => {
afterEach(() => {
+ routerMocks.push.mockClear();
vi.unstubAllEnvs();
});
@@ -99,4 +105,16 @@ describe("AddRoleForm", () => {
expect(screen.queryByText("Manage Alerts")).not.toBeInTheDocument();
expect(screen.queryByText("Manage Billing")).not.toBeInTheDocument();
});
+
+ it("navigates back to roles when cancel is clicked", async () => {
+ // Given
+ const user = userEvent.setup();
+ render();
+
+ // When
+ await user.click(screen.getByRole("button", { name: /cancel/i }));
+
+ // Then
+ expect(routerMocks.push).toHaveBeenCalledWith("/roles");
+ });
});
diff --git a/ui/components/roles/workflow/forms/add-role-form.tsx b/ui/components/roles/workflow/forms/add-role-form.tsx
index ccd32bf031..2ae5db944d 100644
--- a/ui/components/roles/workflow/forms/add-role-form.tsx
+++ b/ui/components/roles/workflow/forms/add-role-form.tsx
@@ -252,7 +252,11 @@ export const AddRoleForm = ({
)}
)}
-
+ router.push("/roles")}
+ />
);
diff --git a/ui/components/roles/workflow/forms/edit-role-form.tsx b/ui/components/roles/workflow/forms/edit-role-form.tsx
index 4c881d6f98..e3e1a3bde8 100644
--- a/ui/components/roles/workflow/forms/edit-role-form.tsx
+++ b/ui/components/roles/workflow/forms/edit-role-form.tsx
@@ -271,7 +271,11 @@ export const EditRoleForm = ({
)}
)}
-
+ router.push("/roles")}
+ />
);
diff --git a/ui/components/shadcn/select/multiselect.test.tsx b/ui/components/shadcn/select/multiselect.test.tsx
index 18a8821be6..6f649d34d5 100644
--- a/ui/components/shadcn/select/multiselect.test.tsx
+++ b/ui/components/shadcn/select/multiselect.test.tsx
@@ -239,6 +239,46 @@ describe("MultiSelect", () => {
expect(screen.getByRole("dialog")).toHaveClass("max-w-[24rem]");
});
+ it("keeps long option lists scrollable inside the dropdown", async () => {
+ // Given
+ const user = userEvent.setup();
+
+ render(
+ {}}>
+
+
+
+
+ {Array.from({ length: 20 }, (_, index) => (
+
+ Account {index}
+
+ ))}
+
+ ,
+ );
+
+ // When
+ await user.click(screen.getByRole("combobox"));
+
+ // Then
+ const list = screen
+ .getByRole("dialog")
+ .querySelector('[data-slot="command-list"]');
+
+ expect(screen.getByRole("dialog")).toHaveStyle({
+ maxHeight:
+ "min(360px, var(--radix-popover-content-available-height, 360px))",
+ });
+ expect(list).toHaveClass("minimal-scrollbar");
+ expect(list).toHaveStyle({
+ maxHeight:
+ "min(300px, var(--radix-popover-content-available-height, 300px))",
+ });
+ expect(list).toHaveClass("overflow-y-auto");
+ expect(list).toHaveClass("overscroll-contain");
+ });
+
it("keeps the legacy clear-all behavior by default", async () => {
const user = userEvent.setup();
const onValuesChange = vi.fn();
diff --git a/ui/components/shadcn/select/multiselect.tsx b/ui/components/shadcn/select/multiselect.tsx
index caf39437ca..a9e1544935 100644
--- a/ui/components/shadcn/select/multiselect.tsx
+++ b/ui/components/shadcn/select/multiselect.tsx
@@ -10,6 +10,7 @@ import {
useEffect,
useRef,
useState,
+ type WheelEvent,
} from "react";
import { Badge } from "@/components/shadcn/badge/badge";
@@ -49,6 +50,10 @@ type MultiSelectContextType = {
};
const MultiSelectContext = createContext(null);
+const stopWheelPropagation = (event: WheelEvent) => {
+ event.stopPropagation();
+};
+
export function MultiSelect({
children,
values,
@@ -335,12 +340,16 @@ export function MultiSelectContent({
-
+
{canSearch ? (
{canSearch && (
diff --git a/ui/components/shadcn/select/select.test.tsx b/ui/components/shadcn/select/select.test.tsx
new file mode 100644
index 0000000000..10fe8ec490
--- /dev/null
+++ b/ui/components/shadcn/select/select.test.tsx
@@ -0,0 +1,63 @@
+import { render, screen } from "@testing-library/react";
+import userEvent from "@testing-library/user-event";
+import { describe, expect, it } from "vitest";
+
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from "./select";
+
+Object.defineProperty(HTMLElement.prototype, "hasPointerCapture", {
+ writable: true,
+ configurable: true,
+ value: () => false,
+});
+
+Object.defineProperty(HTMLElement.prototype, "scrollIntoView", {
+ writable: true,
+ configurable: true,
+ value: () => {},
+});
+
+describe("Select", () => {
+ it("keeps long option lists scrollable inside the dropdown", async () => {
+ // Given
+ const user = userEvent.setup();
+
+ render(
+ ,
+ );
+
+ // When
+ await user.click(screen.getByRole("combobox", { name: /options/i }));
+
+ // Then
+ const viewport = screen
+ .getByRole("listbox")
+ .querySelector('[data-slot="select-viewport"]');
+ expect(screen.getByRole("listbox")).toHaveStyle({
+ maxHeight: "var(--radix-select-content-available-height)",
+ });
+ expect(viewport).toHaveClass("minimal-scrollbar");
+ expect(viewport).toHaveStyle({
+ maxHeight:
+ "min(300px, var(--radix-select-content-available-height, 300px))",
+ });
+ expect(viewport).toHaveClass("overflow-y-auto");
+ expect(viewport).toHaveClass("overscroll-contain");
+ });
+});
diff --git a/ui/components/shadcn/select/select.tsx b/ui/components/shadcn/select/select.tsx
index 73bdfbae28..06face8201 100644
--- a/ui/components/shadcn/select/select.tsx
+++ b/ui/components/shadcn/select/select.tsx
@@ -2,10 +2,14 @@
import * as SelectPrimitive from "@radix-ui/react-select";
import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react";
-import { ComponentProps } from "react";
+import { ComponentProps, type WheelEvent } from "react";
import { cn } from "@/lib/utils";
+const stopWheelPropagation = (event: WheelEvent) => {
+ event.stopPropagation();
+};
+
function Select({
allowDeselect = false,
...props
@@ -83,6 +87,7 @@ function SelectContent({
position = "popper",
align = "start",
width = "default",
+ style,
...props
}: ComponentProps & {
width?: "default" | "wide";
@@ -97,22 +102,32 @@ function SelectContent({
{children}