mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-03-22 03:08:23 +00:00
fix(ui): replace HeroUI dropdowns with Radix ActionDropdown to fix overlay conflict (#9996)
This commit is contained in:
@@ -8,9 +8,14 @@ All notable changes to the **Prowler UI** are documented in this file.
|
||||
|
||||
- Attack Paths: Query list now shows their name and short description, when one is selected it also shows a longer description and an attribution if it has it [(#9983)](https://github.com/prowler-cloud/prowler/pull/9983)
|
||||
|
||||
---
|
||||
|
||||
## [1.18.2] (Prowler UNRELEASED)
|
||||
|
||||
### 🐞 Fixed
|
||||
|
||||
- ProviderTypeSelector crash when an unknown provider type is not present in PROVIDER_DATA [(#9991)](https://github.com/prowler-cloud/prowler/pull/9991)
|
||||
- Infinite memory loop when opening modals from table row action dropdowns caused by HeroUI (React Aria) and Radix Dialog overlay conflict [(#9996)](https://github.com/prowler-cloud/prowler/pull/9996)
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
"use client";
|
||||
|
||||
import {
|
||||
Dropdown,
|
||||
DropdownItem,
|
||||
DropdownMenu,
|
||||
DropdownSection,
|
||||
DropdownTrigger,
|
||||
} from "@heroui/dropdown";
|
||||
import { Pencil, Trash2 } from "lucide-react";
|
||||
|
||||
import { MuteRuleData } from "@/actions/mute-rules/types";
|
||||
import { VerticalDotsIcon } from "@/components/icons";
|
||||
import { Button } from "@/components/shadcn";
|
||||
import {
|
||||
ActionDropdown,
|
||||
ActionDropdownDangerZone,
|
||||
ActionDropdownItem,
|
||||
} from "@/components/shadcn/dropdown";
|
||||
|
||||
interface MuteRuleRowActionsProps {
|
||||
muteRule: MuteRuleData;
|
||||
@@ -26,11 +24,8 @@ export function MuteRuleRowActions({
|
||||
}: MuteRuleRowActionsProps) {
|
||||
return (
|
||||
<div className="flex items-center justify-center px-2">
|
||||
<Dropdown
|
||||
className="border-border-neutral-secondary bg-bg-neutral-secondary border shadow-xl"
|
||||
placement="bottom"
|
||||
>
|
||||
<DropdownTrigger>
|
||||
<ActionDropdown
|
||||
trigger={
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon-sm"
|
||||
@@ -41,44 +36,22 @@ export function MuteRuleRowActions({
|
||||
className="text-text-neutral-secondary"
|
||||
/>
|
||||
</Button>
|
||||
</DropdownTrigger>
|
||||
<DropdownMenu
|
||||
closeOnSelect
|
||||
aria-label="Mute rule actions"
|
||||
color="default"
|
||||
variant="flat"
|
||||
>
|
||||
<DropdownSection title="Actions">
|
||||
<DropdownItem
|
||||
key="edit"
|
||||
description="Edit rule name and reason"
|
||||
textValue="Edit"
|
||||
startContent={
|
||||
<Pencil className="text-default-500 pointer-events-none size-4 shrink-0" />
|
||||
}
|
||||
onPress={() => onEdit(muteRule)}
|
||||
>
|
||||
Edit
|
||||
</DropdownItem>
|
||||
<DropdownItem
|
||||
key="delete"
|
||||
description="Delete this mute rule"
|
||||
textValue="Delete"
|
||||
className="text-danger"
|
||||
color="danger"
|
||||
classNames={{
|
||||
description: "text-danger",
|
||||
}}
|
||||
startContent={
|
||||
<Trash2 className="pointer-events-none size-4 shrink-0" />
|
||||
}
|
||||
onPress={() => onDelete(muteRule)}
|
||||
>
|
||||
Delete
|
||||
</DropdownItem>
|
||||
</DropdownSection>
|
||||
</DropdownMenu>
|
||||
</Dropdown>
|
||||
}
|
||||
>
|
||||
<ActionDropdownItem
|
||||
icon={<Pencil />}
|
||||
label="Edit Mute Rule"
|
||||
onSelect={() => onEdit(muteRule)}
|
||||
/>
|
||||
<ActionDropdownDangerZone>
|
||||
<ActionDropdownItem
|
||||
icon={<Trash2 />}
|
||||
label="Delete Mute Rule"
|
||||
destructive
|
||||
onSelect={() => onDelete(muteRule)}
|
||||
/>
|
||||
</ActionDropdownDangerZone>
|
||||
</ActionDropdown>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -66,30 +66,13 @@ export function DataTableRowActions<T extends FindingRowData>({
|
||||
return [finding.id];
|
||||
};
|
||||
|
||||
const getMuteDescription = (): string => {
|
||||
if (isMuted) {
|
||||
return "This finding is already muted";
|
||||
}
|
||||
const ids = getMuteIds();
|
||||
if (ids.length > 1) {
|
||||
return `Mute ${ids.length} selected findings`;
|
||||
}
|
||||
return "Mute this finding";
|
||||
};
|
||||
|
||||
const getMuteLabel = () => {
|
||||
if (isMuted) return "Muted";
|
||||
if (!isMuted && isCurrentSelected && hasMultipleSelected) {
|
||||
return (
|
||||
<>
|
||||
Mute
|
||||
<span className="ml-1 text-xs text-slate-500">
|
||||
({selectedFindingIds.length})
|
||||
</span>
|
||||
</>
|
||||
);
|
||||
const ids = getMuteIds();
|
||||
if (ids.length > 1) {
|
||||
return `Mute ${ids.length} Findings`;
|
||||
}
|
||||
return "Mute";
|
||||
return "Mute Finding";
|
||||
};
|
||||
|
||||
const handleMuteComplete = () => {
|
||||
@@ -146,7 +129,6 @@ export function DataTableRowActions<T extends FindingRowData>({
|
||||
)
|
||||
}
|
||||
label={getMuteLabel()}
|
||||
description={getMuteDescription()}
|
||||
disabled={isMuted}
|
||||
onSelect={() => {
|
||||
setIsMuteModalOpen(true);
|
||||
@@ -155,7 +137,6 @@ export function DataTableRowActions<T extends FindingRowData>({
|
||||
<ActionDropdownItem
|
||||
icon={<JiraIcon size={20} />}
|
||||
label="Send to Jira"
|
||||
description="Create a Jira issue for this finding"
|
||||
onSelect={() => setIsJiraModalOpen(true)}
|
||||
/>
|
||||
</ActionDropdown>
|
||||
|
||||
@@ -1,24 +1,17 @@
|
||||
"use client";
|
||||
|
||||
import {
|
||||
Dropdown,
|
||||
DropdownItem,
|
||||
DropdownMenu,
|
||||
DropdownSection,
|
||||
DropdownTrigger,
|
||||
} from "@heroui/dropdown";
|
||||
import {
|
||||
AddNoteBulkIcon,
|
||||
DeleteDocumentBulkIcon,
|
||||
EditDocumentBulkIcon,
|
||||
} from "@heroui/shared-icons";
|
||||
import { Row } from "@tanstack/react-table";
|
||||
import clsx from "clsx";
|
||||
import { Eye, Pencil, Trash2 } from "lucide-react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useState } from "react";
|
||||
|
||||
import { VerticalDotsIcon } from "@/components/icons";
|
||||
import { Button } from "@/components/shadcn";
|
||||
import {
|
||||
ActionDropdown,
|
||||
ActionDropdownDangerZone,
|
||||
ActionDropdownItem,
|
||||
} from "@/components/shadcn/dropdown";
|
||||
import { Modal } from "@/components/shadcn/modal";
|
||||
|
||||
import { DeleteForm, EditForm } from "../forms";
|
||||
@@ -27,7 +20,6 @@ interface DataTableRowActionsProps<InvitationProps> {
|
||||
row: Row<InvitationProps>;
|
||||
roles?: { id: string; name: string }[];
|
||||
}
|
||||
const iconClasses = "text-2xl text-default-500 pointer-events-none shrink-0";
|
||||
|
||||
export function DataTableRowActions<InvitationProps>({
|
||||
row,
|
||||
@@ -67,65 +59,36 @@ export function DataTableRowActions<InvitationProps>({
|
||||
</Modal>
|
||||
|
||||
<div className="relative flex items-center justify-end gap-2">
|
||||
<Dropdown
|
||||
className="border-border-neutral-secondary bg-bg-neutral-secondary border shadow-xl"
|
||||
placement="bottom"
|
||||
>
|
||||
<DropdownTrigger>
|
||||
<ActionDropdown
|
||||
trigger={
|
||||
<Button variant="ghost" size="icon-sm" className="rounded-full">
|
||||
<VerticalDotsIcon className="text-slate-400" />
|
||||
</Button>
|
||||
</DropdownTrigger>
|
||||
<DropdownMenu
|
||||
closeOnSelect
|
||||
aria-label="Actions"
|
||||
color="default"
|
||||
variant="flat"
|
||||
>
|
||||
<DropdownSection title="Actions">
|
||||
<DropdownItem
|
||||
key="check-details"
|
||||
description="View invitation details"
|
||||
textValue="Check Details"
|
||||
startContent={<AddNoteBulkIcon className={iconClasses} />}
|
||||
onPress={() =>
|
||||
router.push(`/invitations/check-details?id=${invitationId}`)
|
||||
}
|
||||
>
|
||||
Check Details
|
||||
</DropdownItem>
|
||||
|
||||
<DropdownItem
|
||||
key="edit"
|
||||
description="Allows you to edit the invitation"
|
||||
textValue="Edit Invitation"
|
||||
startContent={<EditDocumentBulkIcon className={iconClasses} />}
|
||||
onPress={() => setIsEditOpen(true)}
|
||||
isDisabled={invitationAccepted === "accepted"}
|
||||
>
|
||||
Edit Invitation
|
||||
</DropdownItem>
|
||||
</DropdownSection>
|
||||
<DropdownSection title="Danger zone">
|
||||
<DropdownItem
|
||||
key="delete"
|
||||
className="text-text-error"
|
||||
color="danger"
|
||||
description="Delete the invitation permanently"
|
||||
textValue="Delete Invitation"
|
||||
startContent={
|
||||
<DeleteDocumentBulkIcon
|
||||
className={clsx(iconClasses, "!text-text-error")}
|
||||
/>
|
||||
}
|
||||
onPress={() => setIsDeleteOpen(true)}
|
||||
isDisabled={invitationAccepted === "accepted"}
|
||||
>
|
||||
Revoke Invitation
|
||||
</DropdownItem>
|
||||
</DropdownSection>
|
||||
</DropdownMenu>
|
||||
</Dropdown>
|
||||
}
|
||||
>
|
||||
<ActionDropdownItem
|
||||
icon={<Eye />}
|
||||
label="Check Details"
|
||||
onSelect={() =>
|
||||
router.push(`/invitations/check-details?id=${invitationId}`)
|
||||
}
|
||||
/>
|
||||
<ActionDropdownItem
|
||||
icon={<Pencil />}
|
||||
label="Edit Invitation"
|
||||
onSelect={() => setIsEditOpen(true)}
|
||||
disabled={invitationAccepted === "accepted"}
|
||||
/>
|
||||
<ActionDropdownDangerZone>
|
||||
<ActionDropdownItem
|
||||
icon={<Trash2 />}
|
||||
label="Revoke Invitation"
|
||||
destructive
|
||||
onSelect={() => setIsDeleteOpen(true)}
|
||||
disabled={invitationAccepted === "accepted"}
|
||||
/>
|
||||
</ActionDropdownDangerZone>
|
||||
</ActionDropdown>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -1,23 +1,17 @@
|
||||
"use client";
|
||||
|
||||
import {
|
||||
Dropdown,
|
||||
DropdownItem,
|
||||
DropdownMenu,
|
||||
DropdownSection,
|
||||
DropdownTrigger,
|
||||
} from "@heroui/dropdown";
|
||||
import {
|
||||
DeleteDocumentBulkIcon,
|
||||
EditDocumentBulkIcon,
|
||||
} from "@heroui/shared-icons";
|
||||
import { Row } from "@tanstack/react-table";
|
||||
import clsx from "clsx";
|
||||
import { Pencil, Trash2 } from "lucide-react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useState } from "react";
|
||||
|
||||
import { VerticalDotsIcon } from "@/components/icons";
|
||||
import { Button } from "@/components/shadcn";
|
||||
import {
|
||||
ActionDropdown,
|
||||
ActionDropdownDangerZone,
|
||||
ActionDropdownItem,
|
||||
} from "@/components/shadcn/dropdown";
|
||||
import { Modal } from "@/components/shadcn/modal";
|
||||
|
||||
import { DeleteGroupForm } from "../forms";
|
||||
@@ -25,7 +19,6 @@ import { DeleteGroupForm } from "../forms";
|
||||
interface DataTableRowActionsProps<ProviderProps> {
|
||||
row: Row<ProviderProps>;
|
||||
}
|
||||
const iconClasses = "text-2xl text-default-500 pointer-events-none shrink-0";
|
||||
|
||||
export function DataTableRowActions<ProviderProps>({
|
||||
row,
|
||||
@@ -47,51 +40,27 @@ export function DataTableRowActions<ProviderProps>({
|
||||
</Modal>
|
||||
|
||||
<div className="relative flex items-center justify-end gap-2">
|
||||
<Dropdown
|
||||
className="border-border-neutral-secondary bg-bg-neutral-secondary border shadow-xl"
|
||||
placement="bottom"
|
||||
>
|
||||
<DropdownTrigger>
|
||||
<ActionDropdown
|
||||
trigger={
|
||||
<Button variant="ghost" size="icon-sm" className="rounded-full">
|
||||
<VerticalDotsIcon className="text-slate-400" />
|
||||
</Button>
|
||||
</DropdownTrigger>
|
||||
<DropdownMenu
|
||||
closeOnSelect
|
||||
aria-label="Actions"
|
||||
color="default"
|
||||
variant="flat"
|
||||
>
|
||||
<DropdownSection title="Actions">
|
||||
<DropdownItem
|
||||
key="edit"
|
||||
description="Allows you to edit the provider group"
|
||||
textValue="Edit Provider Group"
|
||||
startContent={<EditDocumentBulkIcon className={iconClasses} />}
|
||||
onPress={() => router.push(`/manage-groups?groupId=${groupId}`)}
|
||||
>
|
||||
Edit Provider Group
|
||||
</DropdownItem>
|
||||
</DropdownSection>
|
||||
<DropdownSection title="Danger zone">
|
||||
<DropdownItem
|
||||
key="delete"
|
||||
className="text-text-error"
|
||||
color="danger"
|
||||
description="Delete the provider group permanently"
|
||||
textValue="Delete Provider Group"
|
||||
startContent={
|
||||
<DeleteDocumentBulkIcon
|
||||
className={clsx(iconClasses, "!text-text-error")}
|
||||
/>
|
||||
}
|
||||
onPress={() => setIsDeleteOpen(true)}
|
||||
>
|
||||
Delete Provider Group
|
||||
</DropdownItem>
|
||||
</DropdownSection>
|
||||
</DropdownMenu>
|
||||
</Dropdown>
|
||||
}
|
||||
>
|
||||
<ActionDropdownItem
|
||||
icon={<Pencil />}
|
||||
label="Edit Provider Group"
|
||||
onSelect={() => router.push(`/manage-groups?groupId=${groupId}`)}
|
||||
/>
|
||||
<ActionDropdownDangerZone>
|
||||
<ActionDropdownItem
|
||||
icon={<Trash2 />}
|
||||
label="Delete Provider Group"
|
||||
destructive
|
||||
onSelect={() => setIsDeleteOpen(true)}
|
||||
/>
|
||||
</ActionDropdownDangerZone>
|
||||
</ActionDropdown>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -1,25 +1,18 @@
|
||||
"use client";
|
||||
|
||||
import {
|
||||
Dropdown,
|
||||
DropdownItem,
|
||||
DropdownMenu,
|
||||
DropdownSection,
|
||||
DropdownTrigger,
|
||||
} from "@heroui/dropdown";
|
||||
import {
|
||||
AddNoteBulkIcon,
|
||||
DeleteDocumentBulkIcon,
|
||||
EditDocumentBulkIcon,
|
||||
} from "@heroui/shared-icons";
|
||||
import { Row } from "@tanstack/react-table";
|
||||
import clsx from "clsx";
|
||||
import { Pencil, PlugZap, Trash2 } from "lucide-react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useState } from "react";
|
||||
|
||||
import { checkConnectionProvider } from "@/actions/providers/providers";
|
||||
import { VerticalDotsIcon } from "@/components/icons";
|
||||
import { Button } from "@/components/shadcn";
|
||||
import {
|
||||
ActionDropdown,
|
||||
ActionDropdownDangerZone,
|
||||
ActionDropdownItem,
|
||||
} from "@/components/shadcn/dropdown";
|
||||
import { Modal } from "@/components/shadcn/modal";
|
||||
|
||||
import { EditForm } from "../forms";
|
||||
@@ -28,7 +21,6 @@ import { DeleteForm } from "../forms/delete-form";
|
||||
interface DataTableRowActionsProps<ProviderProps> {
|
||||
row: Row<ProviderProps>;
|
||||
}
|
||||
const iconClasses = "text-2xl text-default-500 pointer-events-none shrink-0";
|
||||
|
||||
export function DataTableRowActions<ProviderProps>({
|
||||
row,
|
||||
@@ -53,12 +45,6 @@ export function DataTableRowActions<ProviderProps>({
|
||||
|
||||
const hasSecret = (row.original as any).relationships?.secret?.data;
|
||||
|
||||
// Calculate disabled keys based on conditions
|
||||
const disabledKeys = [];
|
||||
if (!hasSecret || loading) {
|
||||
disabledKeys.push("new");
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal
|
||||
@@ -82,88 +68,52 @@ export function DataTableRowActions<ProviderProps>({
|
||||
</Modal>
|
||||
|
||||
<div className="relative flex items-center justify-end gap-2">
|
||||
<Dropdown
|
||||
className="border-border-neutral-secondary bg-bg-neutral-secondary border shadow-xl"
|
||||
placement="bottom"
|
||||
>
|
||||
<DropdownTrigger>
|
||||
<ActionDropdown
|
||||
trigger={
|
||||
<Button variant="ghost" size="icon-sm" className="rounded-full">
|
||||
<VerticalDotsIcon className="text-slate-400" />
|
||||
</Button>
|
||||
</DropdownTrigger>
|
||||
<DropdownMenu
|
||||
aria-label="Actions"
|
||||
color="default"
|
||||
variant="flat"
|
||||
disabledKeys={disabledKeys}
|
||||
closeOnSelect={false}
|
||||
>
|
||||
<DropdownSection title="Actions">
|
||||
<DropdownItem
|
||||
key={hasSecret ? "update" : "add"}
|
||||
description={
|
||||
hasSecret
|
||||
? "Update the provider credentials"
|
||||
: "Add the provider credentials"
|
||||
}
|
||||
textValue={hasSecret ? "Update Credentials" : "Add Credentials"}
|
||||
startContent={<EditDocumentBulkIcon className={iconClasses} />}
|
||||
onPress={() =>
|
||||
router.push(
|
||||
`/providers/${hasSecret ? "update" : "add"}-credentials?type=${providerType}&id=${providerId}${providerSecretId ? `&secretId=${providerSecretId}` : ""}`,
|
||||
)
|
||||
}
|
||||
closeOnSelect={true}
|
||||
>
|
||||
{hasSecret ? "Update Credentials" : "Add Credentials"}
|
||||
</DropdownItem>
|
||||
<DropdownItem
|
||||
key="new"
|
||||
description={
|
||||
hasSecret && !loading
|
||||
? "Check the provider connection"
|
||||
: loading
|
||||
? "Checking provider connection"
|
||||
: "Add credentials to test the connection"
|
||||
}
|
||||
textValue="Check Connection"
|
||||
startContent={<AddNoteBulkIcon className={iconClasses} />}
|
||||
onPress={handleTestConnection}
|
||||
closeOnSelect={false}
|
||||
>
|
||||
{loading ? "Testing..." : "Test Connection"}
|
||||
</DropdownItem>
|
||||
<DropdownItem
|
||||
key="edit"
|
||||
description="Allows you to edit the provider"
|
||||
textValue="Edit Provider"
|
||||
startContent={<EditDocumentBulkIcon className={iconClasses} />}
|
||||
onPress={() => setIsEditOpen(true)}
|
||||
closeOnSelect={true}
|
||||
>
|
||||
Edit Provider Alias
|
||||
</DropdownItem>
|
||||
</DropdownSection>
|
||||
<DropdownSection title="Danger zone">
|
||||
<DropdownItem
|
||||
key="delete"
|
||||
className="text-text-error"
|
||||
color="danger"
|
||||
description="Delete the provider permanently"
|
||||
textValue="Delete Provider"
|
||||
startContent={
|
||||
<DeleteDocumentBulkIcon
|
||||
className={clsx(iconClasses, "!text-text-error")}
|
||||
/>
|
||||
}
|
||||
onPress={() => setIsDeleteOpen(true)}
|
||||
closeOnSelect={true}
|
||||
>
|
||||
Delete Provider
|
||||
</DropdownItem>
|
||||
</DropdownSection>
|
||||
</DropdownMenu>
|
||||
</Dropdown>
|
||||
}
|
||||
>
|
||||
<ActionDropdownItem
|
||||
icon={<Pencil />}
|
||||
label={hasSecret ? "Update Credentials" : "Add Credentials"}
|
||||
onSelect={() =>
|
||||
router.push(
|
||||
`/providers/${hasSecret ? "update" : "add"}-credentials?type=${providerType}&id=${providerId}${providerSecretId ? `&secretId=${providerSecretId}` : ""}`,
|
||||
)
|
||||
}
|
||||
/>
|
||||
<ActionDropdownItem
|
||||
icon={<PlugZap />}
|
||||
label={loading ? "Testing..." : "Test Connection"}
|
||||
description={
|
||||
hasSecret && !loading
|
||||
? "Check the provider connection"
|
||||
: loading
|
||||
? "Checking provider connection"
|
||||
: "Add credentials to test the connection"
|
||||
}
|
||||
onSelect={(e) => {
|
||||
e.preventDefault();
|
||||
handleTestConnection();
|
||||
}}
|
||||
disabled={!hasSecret || loading}
|
||||
/>
|
||||
<ActionDropdownItem
|
||||
icon={<Pencil />}
|
||||
label="Edit Provider Alias"
|
||||
onSelect={() => setIsEditOpen(true)}
|
||||
/>
|
||||
<ActionDropdownDangerZone>
|
||||
<ActionDropdownItem
|
||||
icon={<Trash2 />}
|
||||
label="Delete Provider"
|
||||
destructive
|
||||
onSelect={() => setIsDeleteOpen(true)}
|
||||
/>
|
||||
</ActionDropdownDangerZone>
|
||||
</ActionDropdown>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -83,7 +83,6 @@ const FailedFindingsBadge = ({ count }: { count: number }) => {
|
||||
// Row actions dropdown
|
||||
const ResourceRowActions = ({ row }: { row: { original: ResourceProps } }) => {
|
||||
const [isDrawerOpen, setIsDrawerOpen] = useState(false);
|
||||
const resourceName = row.original.attributes?.name || "Resource";
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -102,8 +101,7 @@ const ResourceRowActions = ({ row }: { row: { original: ResourceProps } }) => {
|
||||
>
|
||||
<ActionDropdownItem
|
||||
icon={<Eye className="size-5" />}
|
||||
label="View details"
|
||||
description={`View details for ${resourceName}`}
|
||||
label="View Details"
|
||||
onSelect={() => setIsDrawerOpen(true)}
|
||||
/>
|
||||
</ActionDropdown>
|
||||
|
||||
@@ -1,30 +1,23 @@
|
||||
"use client";
|
||||
|
||||
import {
|
||||
Dropdown,
|
||||
DropdownItem,
|
||||
DropdownMenu,
|
||||
DropdownSection,
|
||||
DropdownTrigger,
|
||||
} from "@heroui/dropdown";
|
||||
import {
|
||||
DeleteDocumentBulkIcon,
|
||||
EditDocumentBulkIcon,
|
||||
} from "@heroui/shared-icons";
|
||||
import { Row } from "@tanstack/react-table";
|
||||
import clsx from "clsx";
|
||||
import { Pencil, Trash2 } from "lucide-react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useState } from "react";
|
||||
|
||||
import { VerticalDotsIcon } from "@/components/icons";
|
||||
import { Button } from "@/components/shadcn";
|
||||
import {
|
||||
ActionDropdown,
|
||||
ActionDropdownDangerZone,
|
||||
ActionDropdownItem,
|
||||
} from "@/components/shadcn/dropdown";
|
||||
import { Modal } from "@/components/shadcn/modal";
|
||||
|
||||
import { DeleteRoleForm } from "../workflow/forms";
|
||||
interface DataTableRowActionsProps<RoleProps> {
|
||||
row: Row<RoleProps>;
|
||||
}
|
||||
const iconClasses = "text-2xl text-default-500 pointer-events-none shrink-0";
|
||||
|
||||
export function DataTableRowActions<RoleProps>({
|
||||
row,
|
||||
@@ -43,51 +36,27 @@ export function DataTableRowActions<RoleProps>({
|
||||
<DeleteRoleForm roleId={roleId} setIsOpen={setIsDeleteOpen} />
|
||||
</Modal>
|
||||
<div className="relative flex items-center justify-end gap-2">
|
||||
<Dropdown
|
||||
className="border-border-neutral-secondary bg-bg-neutral-secondary border shadow-xl"
|
||||
placement="bottom"
|
||||
>
|
||||
<DropdownTrigger>
|
||||
<ActionDropdown
|
||||
trigger={
|
||||
<Button variant="ghost" size="icon-sm" className="rounded-full">
|
||||
<VerticalDotsIcon className="text-slate-400" />
|
||||
</Button>
|
||||
</DropdownTrigger>
|
||||
<DropdownMenu
|
||||
closeOnSelect
|
||||
aria-label="Actions"
|
||||
color="default"
|
||||
variant="flat"
|
||||
>
|
||||
<DropdownSection title="Actions">
|
||||
<DropdownItem
|
||||
key="edit"
|
||||
description="Edit the role details"
|
||||
textValue="Edit Role"
|
||||
startContent={<EditDocumentBulkIcon className={iconClasses} />}
|
||||
onPress={() => router.push(`/roles/edit?roleId=${roleId}`)}
|
||||
>
|
||||
Edit Role
|
||||
</DropdownItem>
|
||||
</DropdownSection>
|
||||
<DropdownSection title="Danger zone">
|
||||
<DropdownItem
|
||||
key="delete"
|
||||
className="text-text-error"
|
||||
color="danger"
|
||||
description="Delete the role permanently"
|
||||
textValue="Delete Role"
|
||||
startContent={
|
||||
<DeleteDocumentBulkIcon
|
||||
className={clsx(iconClasses, "!text-text-error")}
|
||||
/>
|
||||
}
|
||||
onPress={() => setIsDeleteOpen(true)}
|
||||
>
|
||||
Delete Role
|
||||
</DropdownItem>
|
||||
</DropdownSection>
|
||||
</DropdownMenu>
|
||||
</Dropdown>
|
||||
}
|
||||
>
|
||||
<ActionDropdownItem
|
||||
icon={<Pencil />}
|
||||
label="Edit Role"
|
||||
onSelect={() => router.push(`/roles/edit?roleId=${roleId}`)}
|
||||
/>
|
||||
<ActionDropdownDangerZone>
|
||||
<ActionDropdownItem
|
||||
icon={<Trash2 />}
|
||||
label="Delete Role"
|
||||
destructive
|
||||
onSelect={() => setIsDeleteOpen(true)}
|
||||
/>
|
||||
</ActionDropdownDangerZone>
|
||||
</ActionDropdown>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -1,22 +1,15 @@
|
||||
"use client";
|
||||
|
||||
import {
|
||||
Dropdown,
|
||||
DropdownItem,
|
||||
DropdownMenu,
|
||||
DropdownSection,
|
||||
DropdownTrigger,
|
||||
} from "@heroui/dropdown";
|
||||
import {
|
||||
// DeleteDocumentBulkIcon,
|
||||
EditDocumentBulkIcon,
|
||||
} from "@heroui/shared-icons";
|
||||
import { Row } from "@tanstack/react-table";
|
||||
import { DownloadIcon } from "lucide-react";
|
||||
import { Download, Pencil } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
|
||||
import { VerticalDotsIcon } from "@/components/icons";
|
||||
import { Button } from "@/components/shadcn";
|
||||
import {
|
||||
ActionDropdown,
|
||||
ActionDropdownItem,
|
||||
} from "@/components/shadcn/dropdown";
|
||||
import { Modal } from "@/components/shadcn/modal";
|
||||
import { useToast } from "@/components/ui";
|
||||
import { downloadScanZip } from "@/lib/helper";
|
||||
@@ -26,7 +19,6 @@ import { EditScanForm } from "../../forms";
|
||||
interface DataTableRowActionsProps<ScanProps> {
|
||||
row: Row<ScanProps>;
|
||||
}
|
||||
const iconClasses = "text-2xl text-default-500 pointer-events-none shrink-0";
|
||||
|
||||
export function DataTableRowActions<ScanProps>({
|
||||
row,
|
||||
@@ -52,46 +44,26 @@ export function DataTableRowActions<ScanProps>({
|
||||
</Modal>
|
||||
|
||||
<div className="relative flex items-center justify-end gap-2">
|
||||
<Dropdown
|
||||
className="border-border-neutral-secondary bg-bg-neutral-secondary border shadow-xl"
|
||||
placement="bottom"
|
||||
>
|
||||
<DropdownTrigger>
|
||||
<ActionDropdown
|
||||
trigger={
|
||||
<Button variant="ghost" size="icon-sm" className="rounded-full">
|
||||
<VerticalDotsIcon className="text-slate-400" />
|
||||
</Button>
|
||||
</DropdownTrigger>
|
||||
<DropdownMenu
|
||||
closeOnSelect
|
||||
aria-label="Actions"
|
||||
color="default"
|
||||
variant="flat"
|
||||
>
|
||||
<DropdownSection title="Download reports">
|
||||
<DropdownItem
|
||||
key="export"
|
||||
description="Available only for completed scans"
|
||||
textValue="Download .zip"
|
||||
startContent={<DownloadIcon className={iconClasses} />}
|
||||
onPress={() => downloadScanZip(scanId, toast)}
|
||||
isDisabled={scanState !== "completed"}
|
||||
>
|
||||
Download .zip
|
||||
</DropdownItem>
|
||||
</DropdownSection>
|
||||
<DropdownSection title="Actions">
|
||||
<DropdownItem
|
||||
key="edit"
|
||||
description="Allows you to edit the scan name"
|
||||
textValue="Edit Scan Name"
|
||||
startContent={<EditDocumentBulkIcon className={iconClasses} />}
|
||||
onPress={() => setIsEditOpen(true)}
|
||||
>
|
||||
Edit scan name
|
||||
</DropdownItem>
|
||||
</DropdownSection>
|
||||
</DropdownMenu>
|
||||
</Dropdown>
|
||||
}
|
||||
>
|
||||
<ActionDropdownItem
|
||||
icon={<Download />}
|
||||
label="Download .zip"
|
||||
description="Available only for completed scans"
|
||||
onSelect={() => downloadScanZip(scanId, toast)}
|
||||
disabled={scanState !== "completed"}
|
||||
/>
|
||||
<ActionDropdownItem
|
||||
icon={<Pencil />}
|
||||
label="Edit Scan Name"
|
||||
onSelect={() => setIsEditOpen(true)}
|
||||
/>
|
||||
</ActionDropdown>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -9,7 +9,6 @@ import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "./dropdown";
|
||||
@@ -17,8 +16,6 @@ import {
|
||||
interface ActionDropdownProps {
|
||||
/** The dropdown trigger element. Defaults to a vertical dots icon button */
|
||||
trigger?: ReactNode;
|
||||
/** Label shown at the top of the dropdown */
|
||||
label?: string;
|
||||
/** Alignment of the dropdown content */
|
||||
align?: "start" | "center" | "end";
|
||||
/** Additional className for the content */
|
||||
@@ -30,7 +27,6 @@ interface ActionDropdownProps {
|
||||
|
||||
export function ActionDropdown({
|
||||
trigger,
|
||||
label = "Actions",
|
||||
align = "end",
|
||||
className,
|
||||
ariaLabel = "Open actions menu",
|
||||
@@ -52,16 +48,10 @@ export function ActionDropdown({
|
||||
<DropdownMenuContent
|
||||
align={align}
|
||||
className={cn(
|
||||
"border-border-neutral-secondary bg-bg-neutral-secondary w-56",
|
||||
"border-border-neutral-secondary bg-bg-neutral-secondary w-56 rounded-xl",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{label && (
|
||||
<>
|
||||
<DropdownMenuLabel>{label}</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
</>
|
||||
)}
|
||||
{children}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
@@ -91,8 +81,8 @@ export function ActionDropdownItem({
|
||||
return (
|
||||
<DropdownMenuItem
|
||||
className={cn(
|
||||
"flex cursor-pointer items-center gap-2",
|
||||
destructive && "text-destructive focus:text-destructive",
|
||||
"flex cursor-pointer items-start gap-2",
|
||||
destructive && "text-text-error-primary focus:text-text-error-primary",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
@@ -100,8 +90,8 @@ export function ActionDropdownItem({
|
||||
{icon && (
|
||||
<span
|
||||
className={cn(
|
||||
"text-muted-foreground shrink-0 [&>svg]:size-5",
|
||||
destructive && "text-destructive",
|
||||
"text-muted-foreground mt-0.5 shrink-0 [&>svg]:size-4",
|
||||
destructive && "text-text-error-primary",
|
||||
)}
|
||||
>
|
||||
{icon}
|
||||
@@ -113,7 +103,7 @@ export function ActionDropdownItem({
|
||||
<span
|
||||
className={cn(
|
||||
"text-muted-foreground text-xs",
|
||||
destructive && "text-destructive/70",
|
||||
destructive && "text-text-error-primary/70",
|
||||
)}
|
||||
>
|
||||
{description}
|
||||
@@ -124,8 +114,18 @@ export function ActionDropdownItem({
|
||||
);
|
||||
}
|
||||
|
||||
// Re-export commonly used components for convenience
|
||||
export {
|
||||
DropdownMenuLabel as ActionDropdownLabel,
|
||||
DropdownMenuSeparator as ActionDropdownSeparator,
|
||||
} from "./dropdown";
|
||||
export function ActionDropdownDangerZone({
|
||||
children,
|
||||
}: {
|
||||
children: ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<>
|
||||
<DropdownMenuSeparator />
|
||||
<span className="text-text-neutral-tertiary px-2 py-1.5 text-xs">
|
||||
Danger zone
|
||||
</span>
|
||||
{children}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
export {
|
||||
ActionDropdown,
|
||||
ActionDropdownDangerZone,
|
||||
ActionDropdownItem,
|
||||
ActionDropdownLabel,
|
||||
ActionDropdownSeparator,
|
||||
} from "./action-dropdown";
|
||||
export {
|
||||
DropdownMenu,
|
||||
|
||||
@@ -1,21 +1,15 @@
|
||||
"use client";
|
||||
|
||||
import {
|
||||
Dropdown,
|
||||
DropdownItem,
|
||||
DropdownMenu,
|
||||
DropdownSection,
|
||||
DropdownTrigger,
|
||||
} from "@heroui/dropdown";
|
||||
import {
|
||||
DeleteDocumentBulkIcon,
|
||||
EditDocumentBulkIcon,
|
||||
} from "@heroui/shared-icons";
|
||||
import { Row } from "@tanstack/react-table";
|
||||
import clsx from "clsx";
|
||||
import { Pencil, Trash2 } from "lucide-react";
|
||||
|
||||
import { VerticalDotsIcon } from "@/components/icons";
|
||||
import { Button } from "@/components/shadcn";
|
||||
import {
|
||||
ActionDropdown,
|
||||
ActionDropdownDangerZone,
|
||||
ActionDropdownItem,
|
||||
} from "@/components/shadcn/dropdown";
|
||||
|
||||
import { EnrichedApiKey } from "./types";
|
||||
|
||||
@@ -25,8 +19,6 @@ interface DataTableRowActionsProps {
|
||||
onRevoke: (apiKey: EnrichedApiKey) => void;
|
||||
}
|
||||
|
||||
const iconClasses = "text-2xl text-default-500 pointer-events-none shrink-0";
|
||||
|
||||
export function DataTableRowActions({
|
||||
row,
|
||||
onEdit,
|
||||
@@ -39,53 +31,29 @@ export function DataTableRowActions({
|
||||
|
||||
return (
|
||||
<div className="relative flex items-center justify-end gap-2">
|
||||
<Dropdown
|
||||
className="border-border-neutral-secondary bg-bg-neutral-secondary border shadow-xl"
|
||||
placement="bottom"
|
||||
>
|
||||
<DropdownTrigger>
|
||||
<ActionDropdown
|
||||
trigger={
|
||||
<Button variant="ghost" size="icon-sm" className="rounded-full">
|
||||
<VerticalDotsIcon className="text-slate-400" />
|
||||
</Button>
|
||||
</DropdownTrigger>
|
||||
<DropdownMenu
|
||||
closeOnSelect
|
||||
aria-label="API Key actions"
|
||||
color="default"
|
||||
variant="flat"
|
||||
>
|
||||
<DropdownSection title="Actions">
|
||||
<DropdownItem
|
||||
key="edit"
|
||||
description="Edit the API key name"
|
||||
textValue="Edit name"
|
||||
startContent={<EditDocumentBulkIcon className={iconClasses} />}
|
||||
onPress={() => onEdit(apiKey)}
|
||||
>
|
||||
Edit name
|
||||
</DropdownItem>
|
||||
</DropdownSection>
|
||||
{canRevoke ? (
|
||||
<DropdownSection title="Danger zone">
|
||||
<DropdownItem
|
||||
key="revoke"
|
||||
className="text-text-error"
|
||||
color="danger"
|
||||
description="Revoke this API key permanently"
|
||||
textValue="Revoke"
|
||||
startContent={
|
||||
<DeleteDocumentBulkIcon
|
||||
className={clsx(iconClasses, "!text-text-error")}
|
||||
/>
|
||||
}
|
||||
onPress={() => onRevoke(apiKey)}
|
||||
>
|
||||
Revoke
|
||||
</DropdownItem>
|
||||
</DropdownSection>
|
||||
) : null}
|
||||
</DropdownMenu>
|
||||
</Dropdown>
|
||||
}
|
||||
>
|
||||
<ActionDropdownItem
|
||||
icon={<Pencil />}
|
||||
label="Edit API Key"
|
||||
onSelect={() => onEdit(apiKey)}
|
||||
/>
|
||||
{canRevoke && (
|
||||
<ActionDropdownDangerZone>
|
||||
<ActionDropdownItem
|
||||
icon={<Trash2 />}
|
||||
label="Revoke API Key"
|
||||
destructive
|
||||
onSelect={() => onRevoke(apiKey)}
|
||||
/>
|
||||
</ActionDropdownDangerZone>
|
||||
)}
|
||||
</ActionDropdown>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,22 +1,16 @@
|
||||
"use client";
|
||||
|
||||
import {
|
||||
Dropdown,
|
||||
DropdownItem,
|
||||
DropdownMenu,
|
||||
DropdownSection,
|
||||
DropdownTrigger,
|
||||
} from "@heroui/dropdown";
|
||||
import {
|
||||
DeleteDocumentBulkIcon,
|
||||
EditDocumentBulkIcon,
|
||||
} from "@heroui/shared-icons";
|
||||
import { Row } from "@tanstack/react-table";
|
||||
import clsx from "clsx";
|
||||
import { Pencil, Trash2 } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
|
||||
import { VerticalDotsIcon } from "@/components/icons";
|
||||
import { Button } from "@/components/shadcn";
|
||||
import {
|
||||
ActionDropdown,
|
||||
ActionDropdownDangerZone,
|
||||
ActionDropdownItem,
|
||||
} from "@/components/shadcn/dropdown";
|
||||
import { Modal } from "@/components/shadcn/modal";
|
||||
|
||||
import { DeleteForm, EditForm } from "../forms";
|
||||
@@ -25,7 +19,6 @@ interface DataTableRowActionsProps<UserProps> {
|
||||
row: Row<UserProps>;
|
||||
roles?: { id: string; name: string }[];
|
||||
}
|
||||
const iconClasses = "text-2xl text-default-500 pointer-events-none shrink-0";
|
||||
|
||||
export function DataTableRowActions<UserProps>({
|
||||
row,
|
||||
@@ -66,51 +59,27 @@ export function DataTableRowActions<UserProps>({
|
||||
</Modal>
|
||||
|
||||
<div className="relative flex items-center justify-end gap-2">
|
||||
<Dropdown
|
||||
className="border-border-neutral-secondary bg-bg-neutral-secondary border shadow-xl"
|
||||
placement="bottom"
|
||||
>
|
||||
<DropdownTrigger>
|
||||
<ActionDropdown
|
||||
trigger={
|
||||
<Button variant="ghost" size="icon-sm" className="rounded-full">
|
||||
<VerticalDotsIcon className="text-slate-400" />
|
||||
</Button>
|
||||
</DropdownTrigger>
|
||||
<DropdownMenu
|
||||
closeOnSelect
|
||||
aria-label="Actions"
|
||||
color="default"
|
||||
variant="flat"
|
||||
>
|
||||
<DropdownSection title="Actions">
|
||||
<DropdownItem
|
||||
key="edit"
|
||||
description="Allows you to edit the user"
|
||||
textValue="Edit User"
|
||||
startContent={<EditDocumentBulkIcon className={iconClasses} />}
|
||||
onPress={() => setIsEditOpen(true)}
|
||||
>
|
||||
Edit User
|
||||
</DropdownItem>
|
||||
</DropdownSection>
|
||||
<DropdownSection title="Danger zone">
|
||||
<DropdownItem
|
||||
key="delete"
|
||||
className="text-text-error"
|
||||
color="danger"
|
||||
description="Delete the user permanently"
|
||||
textValue="Delete User"
|
||||
startContent={
|
||||
<DeleteDocumentBulkIcon
|
||||
className={clsx(iconClasses, "!text-text-error")}
|
||||
/>
|
||||
}
|
||||
onPress={() => setIsDeleteOpen(true)}
|
||||
>
|
||||
Delete User
|
||||
</DropdownItem>
|
||||
</DropdownSection>
|
||||
</DropdownMenu>
|
||||
</Dropdown>
|
||||
}
|
||||
>
|
||||
<ActionDropdownItem
|
||||
icon={<Pencil />}
|
||||
label="Edit User"
|
||||
onSelect={() => setIsEditOpen(true)}
|
||||
/>
|
||||
<ActionDropdownDangerZone>
|
||||
<ActionDropdownItem
|
||||
icon={<Trash2 />}
|
||||
label="Delete User"
|
||||
destructive
|
||||
onSelect={() => setIsDeleteOpen(true)}
|
||||
/>
|
||||
</ActionDropdownDangerZone>
|
||||
</ActionDropdown>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user