From cbab9332a9c7c6d724cd8fe702e1516cfc096d4b Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Tue, 24 Mar 2026 13:35:59 +0700 Subject: [PATCH] feat(app): redesign command autocomplete with detail card and dropdown styling - Add detail card above command list showing full description and argument hint - Compact command rows to single line (label + truncated description) - Match dropdown component styling (surface1 bg, borderAccent border, shadow) --- .../app/src/components/ui/autocomplete.tsx | 190 ++++++++++++------ 1 file changed, 134 insertions(+), 56 deletions(-) diff --git a/packages/app/src/components/ui/autocomplete.tsx b/packages/app/src/components/ui/autocomplete.tsx index a0e5f11be..ad7d2172c 100644 --- a/packages/app/src/components/ui/autocomplete.tsx +++ b/packages/app/src/components/ui/autocomplete.tsx @@ -150,70 +150,136 @@ export function Autocomplete({ ); } + const selectedOption = options[selectedIndex]; + return ( - - { - scrollOffsetRef.current = event.nativeEvent.contentOffset.y; - }} - scrollEventThrottle={16} - style={styles.scrollView} - contentContainerStyle={styles.scrollContent} - keyboardShouldPersistTaps="always" - > - {options.map((option, index) => { - const isSelected = index === selectedIndex; - const optionLabel = removeBoltGlyphs(option.label) ?? option.label; - const optionDetail = removeBoltGlyphs(option.detail); - const optionDescription = removeBoltGlyphs(option.description); - return ( - handleRowLayout(index, event)} - onPress={() => onSelect(option)} - style={({ hovered = false, pressed }) => [ - styles.item, - (hovered || pressed || isSelected) && styles.itemActive, - ]} - > - {option.kind === "directory" || option.kind === "file" ? ( - - {option.kind === "directory" ? ( - - ) : ( - - )} - - ) : null} - - - {optionLabel} - {optionDetail ? {optionDetail} : null} - - {optionDescription ? ( - - {optionDescription} - - ) : null} - - - ); - })} - + + {selectedOption?.kind === "command" && selectedOption.description ? ( + + + {removeBoltGlyphs(selectedOption.label) ?? selectedOption.label} + + + {removeBoltGlyphs(selectedOption.description)} + + {selectedOption.detail ? ( + {removeBoltGlyphs(selectedOption.detail)} + ) : null} + + ) : null} + + { + scrollOffsetRef.current = event.nativeEvent.contentOffset.y; + }} + scrollEventThrottle={16} + style={styles.scrollView} + contentContainerStyle={styles.scrollContent} + keyboardShouldPersistTaps="always" + > + {options.map((option, index) => { + const isSelected = index === selectedIndex; + const optionLabel = removeBoltGlyphs(option.label) ?? option.label; + const optionDescription = removeBoltGlyphs(option.description); + const isFileOrDir = option.kind === "directory" || option.kind === "file"; + return ( + handleRowLayout(index, event)} + onPress={() => onSelect(option)} + style={({ hovered = false, pressed }) => [ + styles.item, + (hovered || pressed || isSelected) && styles.itemActive, + ]} + > + {isFileOrDir ? ( + <> + + {option.kind === "directory" ? ( + + ) : ( + + )} + + + + {optionLabel} + {removeBoltGlyphs(option.detail) ? ( + {removeBoltGlyphs(option.detail)} + ) : null} + + {optionDescription ? ( + + {optionDescription} + + ) : null} + + + ) : ( + + {optionLabel} + {optionDescription ? ( + + {optionDescription} + + ) : null} + + )} + + ); + })} + + ); } const styles = StyleSheet.create(((theme: Theme) => ({ - container: { - backgroundColor: theme.colors.surface0, + outerWrapper: { + gap: theme.spacing[1], + }, + detailCard: { + backgroundColor: theme.colors.surface1, borderWidth: theme.borderWidth[1], - borderColor: theme.colors.border, + borderColor: theme.colors.borderAccent, + borderRadius: theme.borderRadius.lg, + paddingHorizontal: theme.spacing[3], + paddingVertical: theme.spacing[3], + shadowColor: "#000", + shadowOffset: { width: 0, height: 4 }, + shadowOpacity: 0.2, + shadowRadius: 8, + elevation: 8, + }, + detailLabel: { + color: theme.colors.foreground, + fontSize: theme.fontSize.sm, + fontWeight: theme.fontWeight.normal, + }, + detailDescription: { + color: theme.colors.foregroundMuted, + fontSize: theme.fontSize.xs, + marginTop: theme.spacing[1], + }, + detailHint: { + color: theme.colors.foregroundMuted, + fontSize: theme.fontSize.xs, + marginTop: theme.spacing[1], + }, + container: { + backgroundColor: theme.colors.surface1, + borderWidth: theme.borderWidth[1], + borderColor: theme.colors.borderAccent, borderRadius: theme.borderRadius.lg, overflow: "hidden", + shadowColor: "#000", + shadowOffset: { width: 0, height: 4 }, + shadowOpacity: 0.2, + shadowRadius: 8, + elevation: 8, }, scrollView: { flexGrow: 0, @@ -236,12 +302,19 @@ const styles = StyleSheet.create(((theme: Theme) => ({ marginRight: theme.spacing[1], }, itemActive: { - backgroundColor: theme.colors.surface1, + backgroundColor: theme.colors.surface2, }, itemMain: { flex: 1, minWidth: 0, }, + itemMainRow: { + flex: 1, + minWidth: 0, + flexDirection: "row", + alignItems: "center", + gap: theme.spacing[2], + }, itemHeader: { flexDirection: "row", alignItems: "center", @@ -261,6 +334,11 @@ const styles = StyleSheet.create(((theme: Theme) => ({ fontSize: theme.fontSize.xs, marginTop: 2, }, + itemDescriptionInline: { + flex: 1, + color: theme.colors.foregroundMuted, + fontSize: theme.fontSize.xs, + }, emptyItem: { paddingHorizontal: theme.spacing[3], paddingVertical: theme.spacing[3],