fix(app): align workspace file tree rows
Keep the Files and Changes trees on shared icon, label, and trailing rails while applying the directory-chevron optical adjustments at the owning row.
This commit is contained in:
@@ -22,6 +22,9 @@ import {
|
||||
treeRowPaddingLeft,
|
||||
WORKSPACE_FILE_ROW_TRAILING_PADDING,
|
||||
WORKSPACE_FILE_ROW_VERTICAL_PADDING,
|
||||
WORKSPACE_TREE_ICON_LABEL_GAP,
|
||||
WORKSPACE_TREE_ICON_SIZE,
|
||||
WORKSPACE_TREE_LOADING_ICON_SIZE,
|
||||
} from "@/components/tree-primitives";
|
||||
import { LoadingSpinner } from "@/components/ui/loading-spinner";
|
||||
import {
|
||||
@@ -185,10 +188,15 @@ function TreeRowItem({
|
||||
<View style={styles.entryIcon}>
|
||||
{(() => {
|
||||
if (!isDirectory) {
|
||||
return <MaterialFileIcon fileName={entry.name} size={16} />;
|
||||
return <MaterialFileIcon fileName={entry.name} size={WORKSPACE_TREE_ICON_SIZE} />;
|
||||
}
|
||||
if (loading) {
|
||||
return <ThemedLoadingSpinner size="small" uniProps={foregroundMutedColorMapping} />;
|
||||
return (
|
||||
<ThemedLoadingSpinner
|
||||
size={WORKSPACE_TREE_LOADING_ICON_SIZE}
|
||||
uniProps={foregroundMutedColorMapping}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return <TreeChevron expanded={isExpanded} />;
|
||||
})()}
|
||||
@@ -1074,10 +1082,14 @@ const styles = StyleSheet.create((theme) => ({
|
||||
flex: 1,
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
gap: theme.spacing[2],
|
||||
gap: WORKSPACE_TREE_ICON_LABEL_GAP,
|
||||
minWidth: 0,
|
||||
},
|
||||
entryIcon: {
|
||||
width: WORKSPACE_TREE_ICON_SIZE,
|
||||
height: WORKSPACE_TREE_ICON_SIZE,
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
flexShrink: 0,
|
||||
},
|
||||
entryName: {
|
||||
|
||||
@@ -12,6 +12,9 @@ import { inlineUnistylesStyle } from "@/styles/unistyles-inline-style";
|
||||
// drift apart.
|
||||
export const TREE_INDENT_PER_LEVEL = 16;
|
||||
export const WORKSPACE_FILE_ROW_VERTICAL_PADDING = SPACING[1.5];
|
||||
export const WORKSPACE_TREE_ICON_SIZE = 16;
|
||||
export const WORKSPACE_TREE_LOADING_ICON_SIZE = 14;
|
||||
export const WORKSPACE_TREE_ICON_LABEL_GAP = SPACING[2];
|
||||
/**
|
||||
* Trailing glyph rail shared with the explorer X and Changes options chevron.
|
||||
* The extra 2px is optical: text ink ends inside its layout box, while the
|
||||
@@ -25,7 +28,9 @@ export function treeRowPaddingLeft(depth: number): number {
|
||||
return SPACING[3] + depth * TREE_INDENT_PER_LEVEL;
|
||||
}
|
||||
|
||||
const foregroundMutedIconColorMapping = (theme: Theme) => ({ color: theme.colors.foregroundMuted });
|
||||
const foregroundExtraMutedIconColorMapping = (theme: Theme) => ({
|
||||
color: theme.colors.foregroundExtraMuted,
|
||||
});
|
||||
|
||||
const ThemedChevronRight = withUnistyles(ChevronRight);
|
||||
|
||||
@@ -65,7 +70,10 @@ export function TreeChevron({ expanded }: { expanded: boolean }) {
|
||||
: [styles.chevron, styles.chevronCollapsed]
|
||||
}
|
||||
>
|
||||
<ThemedChevronRight size={16} uniProps={foregroundMutedIconColorMapping} />
|
||||
<ThemedChevronRight
|
||||
size={WORKSPACE_TREE_ICON_SIZE}
|
||||
uniProps={foregroundExtraMutedIconColorMapping}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
@@ -79,19 +87,19 @@ const styles = StyleSheet.create((theme: Theme) => ({
|
||||
backgroundColor: theme.colors.surface2,
|
||||
},
|
||||
chevron: {
|
||||
width: 16,
|
||||
height: 16,
|
||||
width: WORKSPACE_TREE_ICON_SIZE,
|
||||
height: WORKSPACE_TREE_ICON_SIZE,
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
flexShrink: 0,
|
||||
},
|
||||
// Lucide's right-pointing path occupies the middle of its square view box.
|
||||
// Move the painted edge—not the 16px layout slot—onto the tree's glyph rail.
|
||||
// Lucide leaves space around the chevron path. Align its painted right edge inside the fixed
|
||||
// slot; the rotated glyph is wider, so it needs half the optical offset.
|
||||
chevronCollapsed: {
|
||||
left: -4,
|
||||
left: 4,
|
||||
},
|
||||
chevronExpanded: {
|
||||
left: -2,
|
||||
left: 2,
|
||||
transform: [{ rotate: "90deg" }],
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
treeRowPaddingLeft,
|
||||
WORKSPACE_FILE_ROW_TRAILING_PADDING,
|
||||
WORKSPACE_FILE_ROW_VERTICAL_PADDING,
|
||||
WORKSPACE_TREE_ICON_LABEL_GAP,
|
||||
} from "@/components/tree-primitives";
|
||||
import { type Theme } from "@/styles/theme";
|
||||
import { inlineUnistylesStyle } from "@/styles/unistyles-inline-style";
|
||||
@@ -80,7 +81,9 @@ export function DiffFolderRow({
|
||||
testID={testID ? `${testID}-toggle` : undefined}
|
||||
>
|
||||
<View style={leftStyle}>
|
||||
<TreeChevron expanded={!collapsed} />
|
||||
<View style={styles.chevronOpticalOffset}>
|
||||
<TreeChevron expanded={!collapsed} />
|
||||
</View>
|
||||
<Text style={styles.folderName} numberOfLines={1}>
|
||||
{displayName}
|
||||
</Text>
|
||||
@@ -115,10 +118,14 @@ const styles = StyleSheet.create((theme: Theme) => ({
|
||||
left: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
gap: theme.spacing[1],
|
||||
gap: WORKSPACE_TREE_ICON_LABEL_GAP,
|
||||
flex: 1,
|
||||
minWidth: 0,
|
||||
},
|
||||
chevronOpticalOffset: {
|
||||
// The Changes directory chevron reads high beside the folder label.
|
||||
transform: [{ translateY: 2 }],
|
||||
},
|
||||
right: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
|
||||
@@ -50,6 +50,8 @@ import {
|
||||
treeRowPaddingLeft,
|
||||
WORKSPACE_FILE_ROW_TRAILING_PADDING,
|
||||
WORKSPACE_FILE_ROW_VERTICAL_PADDING,
|
||||
WORKSPACE_TREE_ICON_LABEL_GAP,
|
||||
WORKSPACE_TREE_ICON_SIZE,
|
||||
} from "@/components/tree-primitives";
|
||||
import { MaterialFileIcon } from "@/components/material-file-icon";
|
||||
import { FileChangeIcon } from "@/components/file-change-icon";
|
||||
@@ -1021,7 +1023,10 @@ const DiffFileHeader = memo(function DiffFileHeader({
|
||||
const fileName = file.path.split("/").pop() ?? file.path;
|
||||
const headerContent = (
|
||||
<>
|
||||
<View ref={dragSourceRef} style={styles.fileHeaderLeft}>
|
||||
<View
|
||||
ref={dragSourceRef}
|
||||
style={showDir ? styles.fileHeaderLeft : [styles.fileHeaderLeft, styles.fileHeaderLeftTree]}
|
||||
>
|
||||
{showDir ? null : (
|
||||
<View style={styles.fileIcon}>
|
||||
<MaterialFileIcon fileName={fileName} size={16} />
|
||||
@@ -3156,6 +3161,9 @@ const styles = StyleSheet.create((theme) => ({
|
||||
flex: 1,
|
||||
minWidth: 0,
|
||||
},
|
||||
fileHeaderLeftTree: {
|
||||
gap: WORKSPACE_TREE_ICON_LABEL_GAP,
|
||||
},
|
||||
fileHeaderRight: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
@@ -3163,6 +3171,8 @@ const styles = StyleSheet.create((theme) => ({
|
||||
flexShrink: 0,
|
||||
},
|
||||
fileIcon: {
|
||||
width: WORKSPACE_TREE_ICON_SIZE,
|
||||
height: WORKSPACE_TREE_ICON_SIZE,
|
||||
flexShrink: 0,
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
|
||||
Reference in New Issue
Block a user