"use client"; import { Popover as PopoverPrimitive } from "@base-ui/react/popover"; import { CheckIcon, MinusIcon, XIcon } from "lucide-react"; import { useTranslations } from "next-intl"; import { useCallback, useEffect, useState } from "react"; import { cn } from "../../../lib/utils"; import { Button, Checkbox, Input, Separator } from "../../../shadcnui"; import { PermissionValue } from "../data/RbacTypes"; /** * Controlled "global" picker. Exactly ONE instance is rendered by * `RbacContainer`; the container holds `activePicker` state and drives this * component's `open` + `anchor` props. Cells themselves are plain clickable * elements — they just open this picker at their own DOM position. * * This replaces the previous design that mounted one `` per cell * (~2,600 total), which was both slow AND caused unreliable click handling * across overlapping outside-click listeners. */ interface RbacPermissionPickerProps { /** Whether the picker is open. Driven by `activePicker !== null` in the container. */ open: boolean; /** DOM element the popup should anchor to. Required when `open`. */ anchor: HTMLElement | null; /** Current effective value of the active cell (true | string | false | undefined). */ value: PermissionValue | undefined; /** True if the active cell is in a role row (enables the "inherit from defaults" button). */ isRoleColumn: boolean; /** Relationship paths the backend reports for the active cell's module. */ knownSegments: string[]; onSetValue: (value: PermissionValue) => void; /** Only meaningful for role rows — clears the token so the row inherits defaults. */ onClear?: () => void; /** Called when the picker should close (outside-click, ESC, etc.). */ onClose: () => void; } export function RbacPermissionPicker({ open, anchor, value, isRoleColumn, knownSegments, onSetValue, onClear, onClose, }: RbacPermissionPickerProps) { const t = useTranslations(); const [customSegment, setCustomSegment] = useState(""); // Reset the custom-segment input when the picker is reopened for a different cell. useEffect(() => { if (!open) setCustomSegment(""); }, [open]); const currentSegments: string[] = typeof value === "string" ? value.split("|").filter(Boolean) : []; const toggleSegment = useCallback( (segment: string) => { const next = currentSegments.includes(segment) ? currentSegments.filter((s) => s !== segment) : [...currentSegments, segment]; if (next.length === 0) onSetValue(false); else onSetValue(next.join("|")); }, [currentSegments, onSetValue], ); const addCustomSegment = useCallback(() => { const segment = customSegment.trim(); if (!segment) return; if (!currentSegments.includes(segment)) { onSetValue([...currentSegments, segment].join("|")); } setCustomSegment(""); }, [customSegment, currentSegments, onSetValue]); return ( { if (!next) onClose(); }} > {/* Quick value */}

{t("rbac.quick_value")}

{/* Inherit from defaults — role rows only */} {isRoleColumn && onClear && ( <> )} {/* Relationship-path builder — always visible so users can type custom paths */}

{t("rbac.relationships")}

{knownSegments.length > 0 && (
{knownSegments.map((segment) => ( ))}
)}
setCustomSegment(e.target.value)} placeholder={t("rbac.custom_segment_placeholder")} className="h-7 tabular-nums text-xs" onKeyDown={(e) => { if (e.key === "Enter") { e.preventDefault(); addCustomSegment(); } }} />
{currentSegments.length > 0 && (

{t("rbac.preview")}

{currentSegments.join("|")}

)}
); } export default RbacPermissionPicker;