* feat(app): open changed files from context menu * fix(app): preserve changed-file row behavior * feat(app): add changed files to open chats * feat(app): share file actions across explorer and changes * feat(app): attach workspace files to focused chat * feat(app): drag workspace files into chat * refactor(app): use file actions menu for changed files * fix(app): open changed-file actions on right click * test(app): cover direct file attachment flow * fix(app): enable add to chat on native * fix(app): align workspace file pane rows * refactor(app): simplify workspace file attachments * fix(app): preserve uploaded file draft attachments --------- Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
140 lines
3.6 KiB
TypeScript
140 lines
3.6 KiB
TypeScript
import { useCallback, useMemo } from "react";
|
|
import {
|
|
View,
|
|
Text,
|
|
Pressable,
|
|
type LayoutChangeEvent,
|
|
type PressableStateCallbackType,
|
|
} from "react-native";
|
|
import { StyleSheet } from "react-native-unistyles";
|
|
import { DiffStat } from "@/components/diff-stat";
|
|
import { FILE_ACTIONS_MENU_WIDTH } from "@/components/file-actions-menu";
|
|
import {
|
|
TreeChevron,
|
|
TreeIndentGuides,
|
|
treeRowPaddingLeft,
|
|
WORKSPACE_FILE_ROW_VERTICAL_PADDING,
|
|
} from "@/components/tree-primitives";
|
|
import { type Theme } from "@/styles/theme";
|
|
import { inlineUnistylesStyle } from "@/styles/unistyles-inline-style";
|
|
|
|
interface DiffFolderRowProps {
|
|
/** full uncompressed directory path — the collapse identity */
|
|
dirPath: string;
|
|
displayName: string;
|
|
depth: number;
|
|
collapsed: boolean;
|
|
additions: number;
|
|
deletions: number;
|
|
onToggle: (dirPath: string) => void;
|
|
onHeightChange?: (height: number) => void;
|
|
testID?: string;
|
|
}
|
|
|
|
function folderRowPressableStyle({
|
|
hovered,
|
|
pressed,
|
|
}: PressableStateCallbackType & { hovered?: boolean }) {
|
|
// Subtle background highlight on hover/press, matching the Files explorer rows
|
|
// (entryRowActive) — no opacity darken.
|
|
return [styles.folderRow, (Boolean(hovered) || pressed) && styles.folderRowActive];
|
|
}
|
|
|
|
export function DiffFolderRow({
|
|
dirPath,
|
|
displayName,
|
|
depth,
|
|
collapsed,
|
|
additions,
|
|
deletions,
|
|
onToggle,
|
|
onHeightChange,
|
|
testID,
|
|
}: DiffFolderRowProps) {
|
|
const handlePress = useCallback(() => {
|
|
onToggle(dirPath);
|
|
}, [dirPath, onToggle]);
|
|
|
|
const handleLayout = useCallback(
|
|
(event: LayoutChangeEvent) => {
|
|
onHeightChange?.(event.nativeEvent.layout.height);
|
|
},
|
|
[onHeightChange],
|
|
);
|
|
|
|
const leftStyle = useMemo(
|
|
() => [styles.left, inlineUnistylesStyle({ paddingLeft: treeRowPaddingLeft(depth) })],
|
|
[depth],
|
|
);
|
|
|
|
const accessibilityState = useMemo(() => ({ expanded: !collapsed }), [collapsed]);
|
|
|
|
return (
|
|
<View style={styles.container} onLayout={handleLayout} testID={testID}>
|
|
<TreeIndentGuides depth={depth} />
|
|
<Pressable
|
|
onPress={handlePress}
|
|
style={folderRowPressableStyle}
|
|
accessibilityRole="button"
|
|
accessibilityState={accessibilityState}
|
|
testID={testID ? `${testID}-toggle` : undefined}
|
|
>
|
|
<View style={leftStyle}>
|
|
<TreeChevron expanded={!collapsed} />
|
|
<Text style={styles.folderName} numberOfLines={1}>
|
|
{displayName}
|
|
</Text>
|
|
</View>
|
|
<View style={styles.right}>
|
|
<DiffStat
|
|
additions={additions}
|
|
deletions={deletions}
|
|
testID={testID ? `${testID}-stat` : undefined}
|
|
/>
|
|
<View style={styles.actionSlot} />
|
|
</View>
|
|
</Pressable>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
const styles = StyleSheet.create((theme: Theme) => ({
|
|
container: {
|
|
overflow: "hidden",
|
|
},
|
|
folderRow: {
|
|
flexDirection: "row",
|
|
alignItems: "center",
|
|
paddingRight: theme.spacing[3],
|
|
paddingVertical: WORKSPACE_FILE_ROW_VERTICAL_PADDING,
|
|
gap: theme.spacing[1],
|
|
minWidth: 0,
|
|
},
|
|
folderRowActive: {
|
|
backgroundColor: theme.colors.surfaceSidebarHover,
|
|
},
|
|
left: {
|
|
flexDirection: "row",
|
|
alignItems: "center",
|
|
gap: theme.spacing[1],
|
|
flex: 1,
|
|
minWidth: 0,
|
|
},
|
|
right: {
|
|
flexDirection: "row",
|
|
alignItems: "center",
|
|
flexShrink: 0,
|
|
gap: theme.spacing[1],
|
|
},
|
|
actionSlot: {
|
|
width: FILE_ACTIONS_MENU_WIDTH,
|
|
},
|
|
folderName: {
|
|
fontSize: theme.fontSize.sm,
|
|
fontWeight: theme.fontWeight.normal,
|
|
color: theme.colors.foreground,
|
|
flexShrink: 1,
|
|
minWidth: 0,
|
|
},
|
|
}));
|