"use client"; import { useSafeIntl } from "@kopexa/i18n"; import { KRN } from "@kopexa/krn"; import { Popover, Tooltip } from "@kopexa/sight"; import { relatedControlChip } from "@kopexa/theme"; import type { ComponentProps } from "react"; import { messages } from "./messages"; // ============================================ // Types // ============================================ export interface MappedControlsProps extends ComponentProps<"div"> { /** Array of control KRN strings */ controls: string[]; /** Maximum number of chips to display before showing overflow */ maxVisible?: number; /** Size variant */ size?: "sm" | "md" | "lg"; /** Make chips interactive (clickable) */ interactive?: boolean; /** Callback when a control chip is clicked */ onControlClick?: (krn: string) => void; /** Show empty state when no controls */ showEmpty?: boolean; } export interface ControlChipProps { /** The KRN string */ krn: string; /** Size variant */ size?: "sm" | "md" | "lg"; /** Make chip interactive */ interactive?: boolean; /** Click handler */ onClick?: () => void; /** Additional class names */ className?: string; } // ============================================ // Helper Functions // ============================================ /** * Parse a KRN string and extract framework + control ID. * Returns formatted string like "iso27001:a-7-1" or the original string if parsing fails. */ function formatControlKrn(krnString: string): { framework: string | null; controlId: string; full: string; } { const parsed = KRN.tryParse(krnString); if (!parsed) { return { framework: null, controlId: krnString, full: krnString }; } const framework = parsed.tryResourceId("frameworks"); const control = parsed.tryResourceId("controls"); if (framework && control) { return { framework, controlId: control, full: `${framework}:${control}`, }; } const basename = parsed.basename(); return { framework: null, controlId: basename || krnString, full: basename || krnString, }; } // ============================================ // Control Chip Component // ============================================ export function ControlChip({ krn, size = "md", interactive = false, onClick, className, }: ControlChipProps) { const styles = relatedControlChip({ size, interactive }); const { controlId, full } = formatControlKrn(krn); // Only show uppercase control ID in chip const displayId = controlId.toUpperCase(); const content = {displayId}; // Use button when interactive, span otherwise const chip = interactive && onClick ? ( ) : ( {content} ); // Wrap in tooltip with full formatted KRN (e.g., "iso27001:a-7-1") return {chip}; } // ============================================ // Mapped Controls Component // ============================================ export function MappedControls({ controls, maxVisible = 2, size = "md", interactive = false, onControlClick, showEmpty = false, className, ...rest }: MappedControlsProps) { const intl = useSafeIntl(); const styles = relatedControlChip({ size, interactive }); // Handle empty state if (controls.length === 0) { if (!showEmpty) return null; return ( {intl.formatMessage(messages.no_controls)} ); } const visibleControls = controls.slice(0, maxVisible); const hiddenCount = controls.length - maxVisible; return (
{visibleControls.map((krn) => ( onControlClick(krn) : undefined} /> ))} {hiddenCount > 0 && ( {intl.formatMessage(messages.more_controls, { count: hiddenCount, })}
{intl.formatMessage(messages.view_all, { count: controls.length, })}
{controls.map((krn) => ( onControlClick(krn) : undefined } /> ))}
)}
); }