import React, { useMemo, useState } from 'react'; import { TextInput, Modal, ScrollView, Pressable, Text as RNText, Dimensions } from 'react-native'; import { Box, HStack, VStack, Text, Pressable as GluestackPressable } from '@gluestack-ui/themed'; import { Ionicons, Feather } from '@expo/vector-icons'; import type { InputToolBarClassNames, InputToolBarProps, RightToolbarItemId, LeftToolbarItemId, MicSendButtonConfig, TemplateButtonConfig, TemplateModalConfig, TemplateModalItem, ToolbarItemConfig, } from './types'; const iconSize = 14; const iconSizeRight = 16; const defaultLeftIconMap: Record = { search: , zap: , lightbulb: , template: null, }; const defaultRightIconMap: Record = { projectSettings: , tag: , chip: , camera: , image: , attach: , mic: , }; function MicSendButton({ hasContent, onSend, onMic, disabled, isLoading, onStop, classNames, }: MicSendButtonConfig & { classNames?: InputToolBarClassNames }) { const isStop = isLoading === true; const isSend = !isStop && hasContent; const handlePress = isStop ? onStop : isSend ? onSend : onMic; const label = isStop ? 'Stop' : isSend ? 'Send' : 'Start voice input'; return ( {isStop ? ( ) : isSend ? ( ) : ( )} ); } function ToolbarIconButton({ item, getDefaultIcon, isLeftGroup, classNames, }: { item: ToolbarItemConfig; getDefaultIcon: (id: string) => React.ReactNode; isLeftGroup?: boolean; classNames?: InputToolBarClassNames; }) { if (item.enabled === false) return null; const icon = item.customButton ?? item.icon ?? getDefaultIcon(item.id); if (item.customButton) { return <>{item.customButton}; } const isDisabled = item.disabled === true || item.loading === true; const isActive = item.active === true; return ( {item.loading ? ( ) : isActive && isLeftGroup && React.isValidElement(icon) ? ( React.cloneElement(icon as React.ReactElement<{ color?: string }>, { color: 'white' }) ) : ( icon )} ); } function LeftSection({ leftItems, leftCustomRender, classNames, }: Pick) { if (leftCustomRender != null) { return ( {leftCustomRender} ); } const left = leftItems ?? []; const leftEnabled = left.filter((item) => item.enabled !== false); if (leftEnabled.length === 0) return null; return ( {leftEnabled.map((item, index) => ( defaultLeftIconMap[id as LeftToolbarItemId] ?? null} isLeftGroup classNames={classNames} /> {index < leftEnabled.length - 1 && } ))} ); } function TemplatePill({ label, count, selectedLabel, onClick, onClearTemplate, disabled, classNames, }: { label: string; count?: number; selectedLabel?: string | null; onClick?: () => void; onClearTemplate?: () => void; disabled?: boolean; classNames?: InputToolBarClassNames; }) { if (selectedLabel) { return ( {selectedLabel} ); } const text = count != null && count > 0 ? `${label} (${count})` : label; return ( {text} ); } function RightSection({ rightItems, rightCustomRender, micSendButton, classNames, }: Pick) { if (rightCustomRender != null) { return ( {rightCustomRender} ); } const right = rightItems ?? []; const rightEnabled = right.filter((item) => item.enabled !== false); const filteredRight = micSendButton != null ? rightEnabled.filter((item) => item.id !== 'mic') : rightEnabled; return ( {filteredRight.map((item) => ( defaultRightIconMap[id as RightToolbarItemId] ?? null} classNames={classNames} /> ))} ); } function DefaultTemplateModal({ isOpen, onClose, templates, selectedId, onSelect, suggestedId, title = 'Choose Template', }: TemplateModalConfig) { const [searchQuery, setSearchQuery] = useState(''); const [categoryFilter, setCategoryFilter] = useState('All'); const categories = useMemo(() => { const cats = new Set(templates.map((t) => t.category || 'Other')); return ['All', ...Array.from(cats).sort()]; }, [templates]); const filteredTemplates = useMemo(() => { return templates.filter((template) => { const searchLower = searchQuery.toLowerCase(); const matchesSearch = !searchQuery || template.label.toLowerCase().includes(searchLower) || (template.description || '').toLowerCase().includes(searchLower); const matchesCategory = categoryFilter === 'All' || template.category === categoryFilter; return matchesSearch && matchesCategory; }); }, [templates, searchQuery, categoryFilter]); if (!isOpen) return null; const modalMinHeight = Dimensions.get('window').height * 0.5; return ( {}} > {title} {categories.map((category) => ( setCategoryFilter(category)} px="$2.5" py="$1" rounded="$md" borderWidth={1} bg={categoryFilter === category ? '$primary50' : '$white'} borderColor={categoryFilter === category ? '$primary200' : '#e4e4e7'} > {category} ))} {filteredTemplates.length > 0 ? ( {filteredTemplates.map((template) => { const isSelected = selectedId === template.id; const isSuggested = suggestedId === template.id; return ( { onSelect(template.id); onClose(); }} p="$3" rounded="$lg" borderWidth={2} borderColor={ isSelected ? '$primary500' : isSuggested ? 'rgba(59, 130, 246, 0.3)' : '#e4e4e7' } bg={ isSelected ? '$primary50' : isSuggested ? 'rgba(59, 130, 246, 0.05)' : 'transparent' } style={{ width: '48%' }} > {template.label} {isSelected && ( )} {template.description && ( {template.description} )} ); })} ) : ( No templates found Try adjusting search or filters )} ); } /** * InputToolBar – UI only. Renders toolbar and optional built-in textarea from props. * All data (leftItems, rightItems, templateButton, inputConfig, micSendButton) and behavior (onClick, onChange, etc.) come from props. */ export function InputToolBar({ className, classNames, inputConfig, topContent, leftItems: leftItemsProp, rightItems: rightItemsProp, templateButton, templateModalConfig, templateModalRender, projectSettingsModalOpen, onProjectSettingsModalClose, projectSettingsModalRender, leftCustomRender, rightCustomRender, micSendButton, children, onContainerClick, }: InputToolBarProps) { const leftItems = leftItemsProp ?? []; const rightItems = rightItemsProp ?? []; const hasInputContent = inputConfig != null || children != null; const effectiveTopContent = inputConfig != null ? topContent : null; const templateModal = templateModalConfig != null && templateModalConfig.isOpen ? templateModalRender != null ? templateModalRender(templateModalConfig) : React.createElement(DefaultTemplateModal, templateModalConfig) : null; const projectSettingsModal = projectSettingsModalOpen === true && onProjectSettingsModalClose != null && projectSettingsModalRender != null ? projectSettingsModalRender({ onClose: onProjectSettingsModalClose }) : null; const containerContent = ( {hasInputContent && ( {effectiveTopContent} {inputConfig != null ? ( ) : ( children )} )} {micSendButton != null && rightCustomRender == null && ( )} {templateButton != null && ( )} ); return ( <> {templateModal} {projectSettingsModal} {hasInputContent && onContainerClick ? ( {containerContent} ) : ( containerContent )} ); } export default InputToolBar;