import React, { useEffect, useRef, type ReactNode } from 'react';
import { Animated, Pressable, ScrollView, Text, View, type StyleProp, type ViewStyle } from 'react-native';
import { RefreshCw, Sparkles, X } from 'lucide-react-native';
import { composerSuggestionStyles as s } from './composerSuggestionStyles';
import { ConnectorBrandIcon, hasConnectorBrandIcon } from '../connectors/connectorBrandIcons';
import { styles } from '../../styles';
import { themedColor } from '../../theme';
import type { SuperagentPromptSuggestion } from '../../types';
const ICON_COLOR = '#A1A1AA';
const SKELETON_WIDTHS = [110, 140, 120];
/**
* Prompt-suggestion chips shown above the composer input — the native mirror of the web
* builder's `ChatInputSuggestionsAbove` in its new design-system variant (DS `Chip
* skin="light"` + ghost `IconButton`s, sparkles restore). Pure UI: the parent
* (`ConversationChat`) owns the suggestion state, fills the draft on select, and reports
* analytics. Renders the chip row when `visible`, a collapsed restore button when
* `showRestore`, and nothing otherwise.
*
* Native intentionally omits two desktop-only web affordances — auto-shortening chip titles
* to fit the row, and the hover-to-preview placeholder — matching the web builder's own
* mobile behavior (both are gated off on touch widths there).
*/
export function ComposerSuggestions({
suggestions,
visible,
showRestore,
refreshing,
onSelect,
onRefresh,
onDismiss,
onRestore,
}: {
suggestions: SuperagentPromptSuggestion[];
visible: boolean;
showRestore: boolean;
refreshing: boolean;
onSelect: (suggestion: SuperagentPromptSuggestion) => void;
onRefresh: () => void;
onDismiss: () => void;
onRestore: () => void;
}) {
if (visible) {
return (
{refreshing
? SKELETON_WIDTHS.map((width, index) => )
: suggestions.map((suggestion, index) => (
))}
);
}
if (showRestore) {
return (
);
}
return null;
}
function SuggestionChip({
suggestion,
onSelect,
}: {
suggestion: SuperagentPromptSuggestion;
onSelect: (suggestion: SuperagentPromptSuggestion) => void;
}) {
const connectorTypes = Array.from(new Set(suggestion.connector_types ?? [])).filter(hasConnectorBrandIcon);
return (
onSelect(suggestion)}
style={({ pressed }) => [s.chip, pressed && styles.pressed]}
>
{connectorTypes.map((type) => (
))}
{suggestion.title}
);
}
function SkeletonChip({ width }: { width: number }) {
const opacity = useRef(new Animated.Value(0.4)).current;
useEffect(() => {
const loop = Animated.loop(
Animated.sequence([
Animated.timing(opacity, { toValue: 0.85, duration: 600, useNativeDriver: true }),
Animated.timing(opacity, { toValue: 0.4, duration: 600, useNativeDriver: true }),
]),
);
loop.start();
return () => loop.stop();
}, [opacity]);
return ;
}
function ActionButton({
accessibilityLabel,
boxed,
children,
disabled,
onPress,
}: {
accessibilityLabel: string;
/** Bordered-box style (the restore affordance); refresh/dismiss default to the ghost look. */
boxed?: boolean;
children: ReactNode;
disabled?: boolean;
onPress: () => void;
}) {
return (
[
boxed ? s.restoreButton : s.actionButton,
disabled && s.actionButtonDisabled,
pressed && styles.pressed,
]}
>
{children}
);
}
function FadeIn({ children, style }: { children: ReactNode; style?: StyleProp }) {
const opacity = useRef(new Animated.Value(0)).current;
useEffect(() => {
Animated.timing(opacity, { toValue: 1, duration: 200, useNativeDriver: true }).start();
}, [opacity]);
return {children};
}