"use client"; import { Button, Badge } from "../ui"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "../ui/tooltip"; import { Check } from 'lucide-react'; import React from 'react'; export interface SelectableOption { id: string; // Selection ID (UUID for platforms, value for others) name: string; // Primary identifier (platform enum or item name) displayName?: string; // Optional display name (for platforms) description?: string; isActive?: boolean; createdAt?: string; updatedAt?: string; icon?: React.ReactNode; color?: string; disabled?: boolean; // If true, option is shown grayed out and not selectable disabledReason?: string; // Tooltip shown on hover when disabled — explains WHY it's unavailable section?: string; // Optional section ID to group options } export interface SectionDefinition { id: string; // Section identifier (matches option.section) label: string; // Display label for the section icon?: React.ReactNode; // Optional icon for the section header description?: string; // Optional description shown under section label } interface PushButtonSelectorProps { options: SelectableOption[]; selectedIds: string[]; onSelectionChange: (selectedIds: string[]) => void; multiSelect?: boolean; title?: string; helpText?: string; className?: string; selectionSummary?: boolean; optional?: boolean; isLoading?: boolean; error?: string | null; skeletonCount?: number; sections?: SectionDefinition[]; // Optional sections for grouping options } // Skeleton component matching external pattern from announcement-form.tsx function PushButtonSelectorSkeleton({ count = 3, hasTitle }: { count?: number; hasTitle?: boolean }) { return (
{hasTitle && (
)}
{[...Array(count)].map((_, i) => (
))}
); } // Error component using ODS error tokens function PushButtonSelectorError({ message, title }: { message: string; title?: string }) { return (
{title && (

{title}

)}
⚠️ {message}
); } export function PushButtonSelector({ options, selectedIds, onSelectionChange, multiSelect = true, title, helpText, className = '', selectionSummary = false, optional = false, isLoading = false, error = null, skeletonCount = 3, sections }: PushButtonSelectorProps) { // LOADING STATE if (isLoading) { return (
); } // ERROR STATE if (error) { return (
); } // VALIDATION: Only filter invalid selectedIds if options are loaded const validSelectedIds = options.length > 0 ? selectedIds.filter(id => options.some(option => option.id === id)) : selectedIds; // Keep all IDs if options not loaded yet // Dev warning for debugging (only when options are loaded) if (process.env.NODE_ENV === 'development' && options.length > 0 && validSelectedIds.length !== selectedIds.length) { const invalidIds = selectedIds.filter(id => !options.some(opt => opt.id === id)); console.warn('[PushButtonSelector] Invalid selected IDs filtered:', invalidIds); } const toggleSelection = (optionId: string) => { if (multiSelect) { const isSelected = validSelectedIds.includes(optionId); if (isSelected) { onSelectionChange(validSelectedIds.filter(id => id !== optionId)); } else { onSelectionChange([...validSelectedIds, optionId]); } } else { // Single select mode onSelectionChange(validSelectedIds.includes(optionId) ? [] : [optionId]); } }; const getSelectedOptions = () => options.filter(option => validSelectedIds.includes(option.id)); // Helper to render a single option const renderOption = (option: SelectableOption) => { const isSelected = validSelectedIds.includes(option.id); const optionEl = (
(!option.disabled || isSelected) && toggleSelection(option.id)} >
{option.icon && (
{option.icon}
)}
{option.displayName || option.name}
{option.description && (
{option.description}
)}
{/* Selection Indicator */}
{isSelected && ( )}
); // Disabled options explain WHY on hover via the unified Tooltip. if (option.disabled && option.disabledReason) { return ( {optionEl} {option.disabledReason} ); } return optionEl; }; // Group options by section if sections are provided const renderOptionsContent = () => { if (!sections || sections.length === 0) { // No sections - render flat list return (
{options.map(renderOption)}
); } // Group options by section const optionsBySection = new Map(); const ungroupedOptions: SelectableOption[] = []; options.forEach(option => { if (option.section) { const existing = optionsBySection.get(option.section) || []; optionsBySection.set(option.section, [...existing, option]); } else { ungroupedOptions.push(option); } }); return (
{/* Render sections in order */} {sections.map(section => { const sectionOptions = optionsBySection.get(section.id) || []; if (sectionOptions.length === 0) return null; return (
{/* Section Header */}
{section.icon && (
{section.icon}
)}
{section.label}
{section.description && (
{section.description}
)}
{/* Section Options */}
{sectionOptions.map(renderOption)}
); })} {/* Render ungrouped options at the end */} {ungroupedOptions.length > 0 && (
{ungroupedOptions.map(renderOption)}
)}
); }; return (
{title && (

{title}

)} {renderOptionsContent()} {/* Selection Summary */} {selectionSummary && validSelectedIds.length > 0 && (
{validSelectedIds.length} {multiSelect ? 'items' : 'item'} selected
{getSelectedOptions().map(option => ( {option.displayName || option.name} ))}
)} {/* Help Text */} {helpText && (
{helpText}
)} {/* Empty State Warning */} {validSelectedIds.length === 0 && title && !optional && (
⚠️ Please select at least one option
)}
); }