Files
paseo/packages/app/src/git/diff-folder-row.tsx
T
bd937850b3 feat: add workspace files to chat from Files and Changes
* 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>
2026-07-22 15:58:24 +02:00

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,
},
}));