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)
This commit is contained in:
@@ -150,70 +150,136 @@ export function Autocomplete({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const selectedOption = options[selectedIndex];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={[styles.container, { maxHeight }]}>
|
<View style={styles.outerWrapper}>
|
||||||
<ScrollView
|
{selectedOption?.kind === "command" && selectedOption.description ? (
|
||||||
ref={scrollRef}
|
<View style={styles.detailCard}>
|
||||||
onLayout={handleScrollViewLayout}
|
<Text style={styles.detailLabel}>
|
||||||
onContentSizeChange={pinToBottom}
|
{removeBoltGlyphs(selectedOption.label) ?? selectedOption.label}
|
||||||
onScroll={(event) => {
|
</Text>
|
||||||
scrollOffsetRef.current = event.nativeEvent.contentOffset.y;
|
<Text style={styles.detailDescription}>
|
||||||
}}
|
{removeBoltGlyphs(selectedOption.description)}
|
||||||
scrollEventThrottle={16}
|
</Text>
|
||||||
style={styles.scrollView}
|
{selectedOption.detail ? (
|
||||||
contentContainerStyle={styles.scrollContent}
|
<Text style={styles.detailHint}>{removeBoltGlyphs(selectedOption.detail)}</Text>
|
||||||
keyboardShouldPersistTaps="always"
|
) : null}
|
||||||
>
|
</View>
|
||||||
{options.map((option, index) => {
|
) : null}
|
||||||
const isSelected = index === selectedIndex;
|
<View style={[styles.container, { maxHeight }]}>
|
||||||
const optionLabel = removeBoltGlyphs(option.label) ?? option.label;
|
<ScrollView
|
||||||
const optionDetail = removeBoltGlyphs(option.detail);
|
ref={scrollRef}
|
||||||
const optionDescription = removeBoltGlyphs(option.description);
|
onLayout={handleScrollViewLayout}
|
||||||
return (
|
onContentSizeChange={pinToBottom}
|
||||||
<Pressable
|
onScroll={(event) => {
|
||||||
key={option.id}
|
scrollOffsetRef.current = event.nativeEvent.contentOffset.y;
|
||||||
onLayout={(event) => handleRowLayout(index, event)}
|
}}
|
||||||
onPress={() => onSelect(option)}
|
scrollEventThrottle={16}
|
||||||
style={({ hovered = false, pressed }) => [
|
style={styles.scrollView}
|
||||||
styles.item,
|
contentContainerStyle={styles.scrollContent}
|
||||||
(hovered || pressed || isSelected) && styles.itemActive,
|
keyboardShouldPersistTaps="always"
|
||||||
]}
|
>
|
||||||
>
|
{options.map((option, index) => {
|
||||||
{option.kind === "directory" || option.kind === "file" ? (
|
const isSelected = index === selectedIndex;
|
||||||
<View style={styles.itemLeading}>
|
const optionLabel = removeBoltGlyphs(option.label) ?? option.label;
|
||||||
{option.kind === "directory" ? (
|
const optionDescription = removeBoltGlyphs(option.description);
|
||||||
<Folder size={14} color={theme.colors.foregroundMuted} />
|
const isFileOrDir = option.kind === "directory" || option.kind === "file";
|
||||||
) : (
|
return (
|
||||||
<File size={14} color={theme.colors.foregroundMuted} />
|
<Pressable
|
||||||
)}
|
key={option.id}
|
||||||
</View>
|
onLayout={(event) => handleRowLayout(index, event)}
|
||||||
) : null}
|
onPress={() => onSelect(option)}
|
||||||
<View style={styles.itemMain}>
|
style={({ hovered = false, pressed }) => [
|
||||||
<View style={styles.itemHeader}>
|
styles.item,
|
||||||
<Text style={styles.itemLabel}>{optionLabel}</Text>
|
(hovered || pressed || isSelected) && styles.itemActive,
|
||||||
{optionDetail ? <Text style={styles.itemDetail}>{optionDetail}</Text> : null}
|
]}
|
||||||
</View>
|
>
|
||||||
{optionDescription ? (
|
{isFileOrDir ? (
|
||||||
<Text style={styles.itemDescription} numberOfLines={1}>
|
<>
|
||||||
{optionDescription}
|
<View style={styles.itemLeading}>
|
||||||
</Text>
|
{option.kind === "directory" ? (
|
||||||
) : null}
|
<Folder size={14} color={theme.colors.foregroundMuted} />
|
||||||
</View>
|
) : (
|
||||||
</Pressable>
|
<File size={14} color={theme.colors.foregroundMuted} />
|
||||||
);
|
)}
|
||||||
})}
|
</View>
|
||||||
</ScrollView>
|
<View style={styles.itemMain}>
|
||||||
|
<View style={styles.itemHeader}>
|
||||||
|
<Text style={styles.itemLabel}>{optionLabel}</Text>
|
||||||
|
{removeBoltGlyphs(option.detail) ? (
|
||||||
|
<Text style={styles.itemDetail}>{removeBoltGlyphs(option.detail)}</Text>
|
||||||
|
) : null}
|
||||||
|
</View>
|
||||||
|
{optionDescription ? (
|
||||||
|
<Text style={styles.itemDescription} numberOfLines={1}>
|
||||||
|
{optionDescription}
|
||||||
|
</Text>
|
||||||
|
) : null}
|
||||||
|
</View>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<View style={styles.itemMainRow}>
|
||||||
|
<Text style={styles.itemLabel}>{optionLabel}</Text>
|
||||||
|
{optionDescription ? (
|
||||||
|
<Text style={styles.itemDescriptionInline} numberOfLines={1}>
|
||||||
|
{optionDescription}
|
||||||
|
</Text>
|
||||||
|
) : null}
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
</Pressable>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</ScrollView>
|
||||||
|
</View>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const styles = StyleSheet.create(((theme: Theme) => ({
|
const styles = StyleSheet.create(((theme: Theme) => ({
|
||||||
container: {
|
outerWrapper: {
|
||||||
backgroundColor: theme.colors.surface0,
|
gap: theme.spacing[1],
|
||||||
|
},
|
||||||
|
detailCard: {
|
||||||
|
backgroundColor: theme.colors.surface1,
|
||||||
borderWidth: theme.borderWidth[1],
|
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,
|
borderRadius: theme.borderRadius.lg,
|
||||||
overflow: "hidden",
|
overflow: "hidden",
|
||||||
|
shadowColor: "#000",
|
||||||
|
shadowOffset: { width: 0, height: 4 },
|
||||||
|
shadowOpacity: 0.2,
|
||||||
|
shadowRadius: 8,
|
||||||
|
elevation: 8,
|
||||||
},
|
},
|
||||||
scrollView: {
|
scrollView: {
|
||||||
flexGrow: 0,
|
flexGrow: 0,
|
||||||
@@ -236,12 +302,19 @@ const styles = StyleSheet.create(((theme: Theme) => ({
|
|||||||
marginRight: theme.spacing[1],
|
marginRight: theme.spacing[1],
|
||||||
},
|
},
|
||||||
itemActive: {
|
itemActive: {
|
||||||
backgroundColor: theme.colors.surface1,
|
backgroundColor: theme.colors.surface2,
|
||||||
},
|
},
|
||||||
itemMain: {
|
itemMain: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
minWidth: 0,
|
minWidth: 0,
|
||||||
},
|
},
|
||||||
|
itemMainRow: {
|
||||||
|
flex: 1,
|
||||||
|
minWidth: 0,
|
||||||
|
flexDirection: "row",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: theme.spacing[2],
|
||||||
|
},
|
||||||
itemHeader: {
|
itemHeader: {
|
||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
@@ -261,6 +334,11 @@ const styles = StyleSheet.create(((theme: Theme) => ({
|
|||||||
fontSize: theme.fontSize.xs,
|
fontSize: theme.fontSize.xs,
|
||||||
marginTop: 2,
|
marginTop: 2,
|
||||||
},
|
},
|
||||||
|
itemDescriptionInline: {
|
||||||
|
flex: 1,
|
||||||
|
color: theme.colors.foregroundMuted,
|
||||||
|
fontSize: theme.fontSize.xs,
|
||||||
|
},
|
||||||
emptyItem: {
|
emptyItem: {
|
||||||
paddingHorizontal: theme.spacing[3],
|
paddingHorizontal: theme.spacing[3],
|
||||||
paddingVertical: theme.spacing[3],
|
paddingVertical: theme.spacing[3],
|
||||||
|
|||||||
Reference in New Issue
Block a user